<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sibelius Seraphini</title>
    <description>The latest articles on Forem by Sibelius Seraphini (@sibelius).</description>
    <link>https://forem.com/sibelius</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F154349%2F161ffd23-6c84-4cc1-91e8-3e9d6f8f7b5d.png</url>
      <title>Forem: Sibelius Seraphini</title>
      <link>https://forem.com/sibelius</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sibelius"/>
    <language>en</language>
    <item>
      <title>Creating a Certificate X.509 expiration Dashboard using Grafana</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Mon, 05 Jan 2026 12:49:08 +0000</pubDate>
      <link>https://forem.com/woovi/creating-a-certificate-x509-expiration-dashboard-using-grafana-211k</link>
      <guid>https://forem.com/woovi/creating-a-certificate-x509-expiration-dashboard-using-grafana-211k</guid>
      <description>&lt;p&gt;At Woovi, we need to manage many X.509 certificates that are not only related to site domains. We have certificates that enable mTLS connections, allow us to sign ISO 20022 XML messages, and provide some A1 and A3 certificates to emit electronic invoices (notas fiscais) and access certain Central Bank and government systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current state of certificate expiration tools
&lt;/h2&gt;

&lt;p&gt;Most certificate expiration tools are focused on renewing site domain certificates. My problem is just to check the expiration of X.509 without auto-renew, as the renewal process requires a real person verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  x509-certificate-exporter + Grafana Dashboard
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/enix/x509-certificate-exporter" rel="noopener noreferrer"&gt;x509-certificate-exporter&lt;/a&gt; is a Prometheus exporter that can read X.509 certificates from many sources, like files, Kubernetes configs, and secrets.&lt;/p&gt;

&lt;p&gt;Here is a basic deployment to watch files&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x509-certificate-exporter&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x509-certificate-exporter&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RollingUpdate&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x509-certificate-exporter&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x509-certificate-exporter&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x509-certificate-exporter&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;enix/x509-certificate-exporter:latest&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9793&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certs&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/certs&lt;/span&gt;
              &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--debug&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--watch-dir=/etc/certs&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certs&lt;/span&gt;
          &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x509-certificates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are going to use Kustomization to generate the configmaps from X.509 files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kustomize.config.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kustomization&lt;/span&gt;

&lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x509-certificate-exporter&lt;/span&gt;

&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deployment.yaml&lt;/span&gt;

&lt;span class="na"&gt;configMapGenerator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x509-certificates&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certs/cert-a.crt&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certs/cert-b.crt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply this kustomization like this &lt;code&gt;kubectl apply -k /deployments/x509-certificate-exporter&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add this Grafana dashboard &lt;a href="https://grafana.com/grafana/dashboards/13922-certificates-expiration-x509-certificate-exporter/" rel="noopener noreferrer"&gt;Certificates Expiration (X509 Certificate Exporter)&lt;/a&gt; and you are all set.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5jfnrarhspf2bso72ut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5jfnrarhspf2bso72ut.png" alt="Sample Dashboard" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;Kubernetes, Prometheus, and Grafana are versatile tools that enable you to build a custom dashboard to solve common problems in many companies.&lt;br&gt;
Set this up in your company and avoid having certificate expiration issues forever.&lt;br&gt;
The next step would be to set up alerts to renew certificates before the expiration date.&lt;/p&gt;

&lt;p&gt;What monitoring solutions are you using in your company?&lt;/p&gt;




&lt;p&gt;Woovi&lt;br&gt;
&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://woovi.com/jobs/" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>x509</category>
      <category>certificate</category>
      <category>grafana</category>
      <category>devops</category>
    </item>
    <item>
      <title>Saving Terraform State in S3</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Thu, 04 Dec 2025 12:55:08 +0000</pubDate>
      <link>https://forem.com/woovi/saving-terraform-state-in-s3-4ja7</link>
      <guid>https://forem.com/woovi/saving-terraform-state-in-s3-4ja7</guid>
      <description>&lt;p&gt;Terraform is a solution to provision infrastructure using code (IaC).&lt;br&gt;
Terraform stores the infrastructure generated in a state.&lt;br&gt;
This state maps the real world and metadata to Terraform.&lt;br&gt;
This ensures that you won't duplicate or recreate existing resources.&lt;/p&gt;
&lt;h2&gt;
  
  
  Storing Terraform State in an S3 bucket
&lt;/h2&gt;

&lt;p&gt;You can use S3 as the backend of the state like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 4.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;

  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-state-dev"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-key"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
    &lt;span class="nx"&gt;shared_credentials_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~/.aws/credentials"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can have a single key to store all your Terraform state, or you can have one key for each Terraform state.&lt;/p&gt;

&lt;p&gt;You can start your Terraform using &lt;code&gt;terraform init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After any &lt;code&gt;terraform apply&lt;/code&gt; or &lt;code&gt;terraform apply&lt;/code&gt; the state in your S3 will be updated.&lt;/p&gt;

&lt;p&gt;At Woovi, we decided to have one key per Terraform provision.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to migrate existing local state?
&lt;/h2&gt;

&lt;p&gt;You can use this command to migrate existing local or another backend state to your S3 bucket&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init &lt;span class="nt"&gt;-migrate-state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  In Resume
&lt;/h2&gt;

&lt;p&gt;If you lose your Terraform state, this can cause significant trouble.&lt;br&gt;
Keeping them in an S3 bucket can make it shareable across your team.&lt;/p&gt;




&lt;p&gt;Woovi&lt;br&gt;
&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://woovi.com/jobs/" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>s3</category>
    </item>
    <item>
      <title>Creating a S3 bucket using Terraform</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Wed, 03 Dec 2025 10:17:55 +0000</pubDate>
      <link>https://forem.com/woovi/creating-a-s3-bucket-using-terraform-4kgb</link>
      <guid>https://forem.com/woovi/creating-a-s3-bucket-using-terraform-4kgb</guid>
      <description>&lt;p&gt;S3 is the de facto and most well-known solution for object storage.&lt;br&gt;
Object storage allows you to store files in a specific bucket and key.&lt;/p&gt;

&lt;p&gt;When your application is small, you just need to manage a few S3 buckets, and you can manage them using the AWS UI.&lt;/p&gt;

&lt;p&gt;The problem is how to reproduce the same S3 bucket configuration over and over again, and from staging to production. &lt;br&gt;
Each S3 bucket has specific needs.&lt;/p&gt;

&lt;p&gt;It is also hard to see how all the S3 buckets are configured or how to change some configurations.&lt;/p&gt;
&lt;h2&gt;
  
  
  S3 using Terraform
&lt;/h2&gt;

&lt;p&gt;Terraform enables you to declare your infrastructure as code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 4.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create S3 bucket for Terraform state&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"mybucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mybucket"&lt;/span&gt;

  &lt;span class="nx"&gt;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mybucket"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Enable versioning (so you can roll back state if needed)&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_versioning"&lt;/span&gt; &lt;span class="s2"&gt;"versioning"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mybucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;versioning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enabled"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Enable server-side encryption by default&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_server_side_encryption_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"sse"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mybucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;apply_server_side_encryption_by_default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sse_algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AES256"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Block public access&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_public_access_block"&lt;/span&gt; &lt;span class="s2"&gt;"block"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mybucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;block_public_acls&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_policy&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ignore_public_acls&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;restrict_public_buckets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just need to type&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And your S3 bucket is created.&lt;/p&gt;

&lt;p&gt;If you modify your Terraform file and run &lt;code&gt;terraform apply&lt;/code&gt; , it will show what changed and only apply the modifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;Using Terraform to provision your infrastructure can help you go from simple S3 buckets to very complex applications.&lt;/p&gt;




&lt;p&gt;Woovi&lt;br&gt;
&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://woovi.com/jobs/" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>s3</category>
      <category>objectstorage</category>
      <category>devops</category>
    </item>
    <item>
      <title>Copy and Paste on Proxmox VM</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Tue, 02 Dec 2025 10:52:48 +0000</pubDate>
      <link>https://forem.com/woovi/copy-and-paste-on-proxmox-vm-15il</link>
      <guid>https://forem.com/woovi/copy-and-paste-on-proxmox-vm-15il</guid>
      <description>&lt;p&gt;Copy and paste does not work well on Proxmox KVM on the UI.&lt;br&gt;
You can try to connect using a serial port to enable copy and paste over the UI.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using a Script
&lt;/h2&gt;

&lt;p&gt;We created a script to simulate keyboard typing and paste data over KVM when using the UI.&lt;/p&gt;

&lt;p&gt;Open your Chrome Dev Tools, and paste this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;KeyboardEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;capsLockOn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SHIFT_NEEDED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z!@#$%^&amp;amp;*()_+{}:"&amp;lt;&amp;gt;?~|&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;needsShift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SHIFT_NEEDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isUpperCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needsShift&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Shift&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isUpperCase&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;capsLockOn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
      &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needsShift&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;simulateKeyEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Shift&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CapsLock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;capsLockOn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;capsLockOn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Caps Lock state changed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;capsLockOn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;typeChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;sendKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;typeChar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Adjust delay as needed&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;typeChar&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To paste something, you first need to focus on the UI, and then type &lt;code&gt;cp('mydata')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can use this when you don't have access to the VM over SSH.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Summary
&lt;/h2&gt;

&lt;p&gt;If you are creative enough, you can solve these limitations.&lt;/p&gt;




&lt;p&gt;Woovi&lt;br&gt;
&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://woovi.com/jobs/" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>proxmox</category>
      <category>canvas</category>
      <category>script</category>
    </item>
    <item>
      <title>Using Terraform to create LXC in Proxmox</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Mon, 01 Dec 2025 11:40:44 +0000</pubDate>
      <link>https://forem.com/woovi/using-terraform-to-create-lxc-in-proxmox-496h</link>
      <guid>https://forem.com/woovi/using-terraform-to-create-lxc-in-proxmox-496h</guid>
      <description>&lt;p&gt;Terraform is a well-known solution for defining, provisioning, and managing Infrastructure as Code (IaC).&lt;br&gt;
Most of the content out there focuses on major clouds as AWS, GCP, and Azure.&lt;br&gt;
I'd like to focus on Proxmox, an open source hypervisor used in a lot of home labs and also in production for companies that follow the baremetal approach, like Woovi.&lt;/p&gt;
&lt;h2&gt;
  
  
  Proxmox Infrastructure
&lt;/h2&gt;

&lt;p&gt;Proxmox is a virtualization platform. You can create VMs and LXC (Linux Containers) using the UI, but when your team grows, and you want to be able to reproduce the same staging environment at production, you need to use IaC.&lt;br&gt;
IaC is also great to make sure something is reproducible.&lt;/p&gt;
&lt;h2&gt;
  
  
  VMs vs LXC
&lt;/h2&gt;

&lt;p&gt;You can think of LXC as simpler and lighter VMs.&lt;br&gt;
LXC provides less isolation, and some software can't run on top of it easily, like microk8s nodes.&lt;br&gt;
We are going to focus on LXC to make this article simpler.&lt;/p&gt;
&lt;h2&gt;
  
  
  Provisioning using Terraform
&lt;/h2&gt;

&lt;p&gt;You can have the whole definition in the same file or split it into many files in the same folder.&lt;/p&gt;

&lt;p&gt;First, define the provider Proxmox from Telmate.&lt;/p&gt;

&lt;p&gt;Also, define the Proxmox provider variable like api_url, token ID, and token secret.&lt;br&gt;
You can follow this documentation &lt;a href="https://registry.terraform.io/providers/Terraform-for-Proxmox/proxmox/latest/docs" rel="noopener noreferrer"&gt;terraform for proxmox&lt;/a&gt; to generate the token ID and token secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;proxmox&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"telmate/proxmox"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3.0.2-rc04"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"proxmox"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;pm_api_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://myproxmoxnode:8006/api2/json"&lt;/span&gt;
  &lt;span class="nx"&gt;pm_tls_insecure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;pm_log_enable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;pm_debug&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;pm_api_token_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PM_API_TOKEN_ID&lt;/span&gt;
  &lt;span class="nx"&gt;pm_api_token_secret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PM_API_TOKEN_SECRET&lt;/span&gt;
  &lt;span class="nx"&gt;pm_log_levels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;_default&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"debug"&lt;/span&gt;
    &lt;span class="nx"&gt;_capturelog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"PM_API_TOKEN_ID"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"PM_API_TOKEN_SECRET"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;define the LXC resource&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"proxmox_lxc"&lt;/span&gt; &lt;span class="s2"&gt;"mongo-1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;target_node&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev1"&lt;/span&gt;
  &lt;span class="nx"&gt;hostname&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mongo1"&lt;/span&gt;
  &lt;span class="nx"&gt;ostemplate&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst"&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LXC_PASSWORD&lt;/span&gt;

  &lt;span class="nx"&gt;unprivileged&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;cores&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;
  &lt;span class="nx"&gt;swap&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;

  &lt;span class="nx"&gt;rootfs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"local-lvm"&lt;/span&gt;
    &lt;span class="nx"&gt;size&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"50G"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eth0"&lt;/span&gt;
    &lt;span class="nx"&gt;bridge&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vmbr0"&lt;/span&gt;
    &lt;span class="nx"&gt;ip&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.99.90.2/16"&lt;/span&gt;
    &lt;span class="nx"&gt;gw&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.99.0.3"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ssh_public_keys&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"~/.ssh/id_ansible.pub"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;ini&lt;/code&gt; to initialize Terraform&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;apply&lt;/code&gt; to provision the lxc resource&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run destroy to destroy everything&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this setup, you need to save the local Terraform files to avoid recreating the resources. For production, the best approach is to keep  Terraform state in an S3 bucket&lt;/p&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;You don't need to start your MVP Startup using Terraform to provision everything in your Proxmox. &lt;br&gt;
My recommendation is to keep improving automation as you and your team grow.&lt;br&gt;
After you get the first Terraform working, the rest gets easier.&lt;/p&gt;

&lt;p&gt;How are you using Terraform in your company?&lt;/p&gt;




&lt;p&gt;Woovi&lt;br&gt;
&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://woovi.com/jobs/" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>proxmox</category>
      <category>baremetal</category>
      <category>iac</category>
    </item>
    <item>
      <title>A Modern Node.js + TypeScript Setup for 2025 🚀</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Wed, 29 Jan 2025 11:06:32 +0000</pubDate>
      <link>https://forem.com/woovi/a-modern-nodejs-typescript-setup-for-2025-nlk</link>
      <guid>https://forem.com/woovi/a-modern-nodejs-typescript-setup-for-2025-nlk</guid>
      <description>&lt;p&gt;With the latest features and tools in Node.js, setting up a modern TypeScript project has never been easier—or more exciting. This guide will show you how to leverage the newest Node.js capabilities to create a lightweight and efficient development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Choose This Setup?
&lt;/h2&gt;

&lt;p&gt;This setup emphasizes simplicity, native features, and minimal dependencies by leveraging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native ESM (ECMAScript Modules) for cleaner, modern syntax.&lt;/li&gt;
&lt;li&gt;Experimental TypeScript Stripping (--experimental-strip-types) for running TypeScript files natively.&lt;/li&gt;
&lt;li&gt;Built-in File Watching without third-party tools like nodemon&lt;/li&gt;
&lt;li&gt;Native Environment Variable Support with .env files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Explore the repo for this setup: &lt;a href="https://github.com/sibelius/esm-pure-experimental-strip-types" rel="noopener noreferrer"&gt;esm-pure-experimental-strip-types&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node.js ESM
&lt;/h2&gt;

&lt;p&gt;ECMAScript Modules (ESM) bring modern syntax with import and export.&lt;/p&gt;

&lt;p&gt;Benefits of ESM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance: Static analysis of imports/exports improves optimization compared to CommonJS (CJS).&lt;/li&gt;
&lt;li&gt;Simplified Module Resolution: Requires explicit file extensions, speeding up module resolution.
For example, instead of:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./module.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Experimental TypeScript stripping (--experimental-strip-types)
&lt;/h2&gt;

&lt;p&gt;The --experimental-strip-types flag lets you run .ts files directly in Node.js, eliminating the need for transpilers like Babel or SWC. Here's how to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--experimental-strip-types&lt;/span&gt; index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use ESM syntax.&lt;/li&gt;
&lt;li&gt;Include explicit file extensions (e.g., .ts) in imports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a sample tsconfig.json to get you started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://json.schemastore.org/tsconfig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"forceConsistentCasingInFileNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isolatedModules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolveJsonModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rootDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noEmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowImportingTsExtensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"verbatimModuleSyntax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"incremental"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key Configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allowImportingTsExtensions: Ensures .ts extensions are required in imports.&lt;/li&gt;
&lt;li&gt;verbatimModuleSyntax: This avoids altering your import/export syntax.
By using --experimental-strip-types, you can bypass tools like Babel, SWC, Webpack, or TSX, streamlining your toolchain significantly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built-in File Watching (--watch)
&lt;/h2&gt;

&lt;p&gt;Forget nodemon! Node.js now supports file watching natively. Use the --watch flag for live reloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--watch&lt;/span&gt; index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster and more efficient than nodemon.&lt;/li&gt;
&lt;li&gt;Native support means fewer dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Native Environment Variable Support (--env-file)
&lt;/h2&gt;

&lt;p&gt;You no longer need packages like dotenv or dotenv-safe to handle .env files. Simply use the --env-file flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--env-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.env index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seamless integration with .env files.&lt;/li&gt;
&lt;li&gt;Fully native functionality, reducing dependency bloat.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This modern Node.js + TypeScript setup eliminates unnecessary complexity by leveraging the latest features. With native ESM, experimental TypeScript stripping, built-in file watching, and .env support, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce dependencies.&lt;/li&gt;
&lt;li&gt;Improve performance.&lt;/li&gt;
&lt;li&gt;Simplify your workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By embracing these tools, you’ll boost productivity and focus on building, not configuring.&lt;/p&gt;

&lt;p&gt;What are your favorite Node.js + TypeScript features? Let me know in the comments!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://github.com/woovibr/jobs" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>node</category>
      <category>ts</category>
      <category>simple</category>
    </item>
    <item>
      <title>"ngrok" from scratch</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Tue, 28 Jan 2025 11:19:07 +0000</pubDate>
      <link>https://forem.com/woovi/ngrok-from-scratch-be3</link>
      <guid>https://forem.com/woovi/ngrok-from-scratch-be3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ngrok&lt;/code&gt; is a fantastic tool for exposing local servers to the internet with minimal effort. However, it’s not always an ideal choice due to cost, licensing, or privacy concerns. In this article, we’ll recreate ngrok-like functionality using open-source tools: NGINX, Kubernetes, Docker, and OpenSSH. By the end, you'll have a custom, self-hosted tunnel that forwards traffic from the internet to your local machine.&lt;/p&gt;

&lt;p&gt;Here’s a high-level overview of the architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprky7aj5r6180ayksvfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprky7aj5r6180ayksvfs.png" alt="Archicture" width="800" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Woovi’s Specific Needs for Tunneling
&lt;/h2&gt;

&lt;p&gt;At Woovi, we developed a Shopify payment app that requires fixed URLs during development. Using ngrok presented a challenge because each developer generated a unique URL. Changing these URLs required re-submitting for Shopify’s review, even for development, causing significant delays.&lt;/p&gt;

&lt;p&gt;To solve this problem, we created a solution that allows any developer to forward their local server to a consistent, custom domain, such as shopify.ourdomain.com. This eliminated the URL change issue, saving valuable time and improving the development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up OpenSSH + NGINX
&lt;/h2&gt;

&lt;p&gt;The first step is to create a Docker image that includes both OpenSSH and NGINX. OpenSSH allows remote SSH connections, while NGINX will handle HTTP traffic forwarding.&lt;/p&gt;

&lt;p&gt;Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;FROM alpine:latest&lt;/span&gt;

&lt;span class="s"&gt;RUN apk add --no-cache \&lt;/span&gt;
    &lt;span class="s"&gt;openssh \&lt;/span&gt;
    &lt;span class="s"&gt;nginx \&lt;/span&gt;
    &lt;span class="s"&gt;bash &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;mkdir -p /run/nginx &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;mkdir -p /root/.ssh&lt;/span&gt;

&lt;span class="c1"&gt;# Generate SSH host keys&lt;/span&gt;
&lt;span class="s"&gt;RUN ssh-keygen -A&lt;/span&gt;

&lt;span class="c1"&gt;# Set the root password to '123456'&lt;/span&gt;
&lt;span class="s"&gt;RUN echo "root:woovi" | chpasswd&lt;/span&gt;

&lt;span class="s"&gt;RUN mkdir /var/run/sshd &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;ssh-keygen -A &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "PermitRootLogin yes" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "PasswordAuthentication yes" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "GatewayPorts yes" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "AllowTcpForwarding yes" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "PermitOpen any" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "MaxAuthTries 6" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "UseDNS no" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "AllowUsers root" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "TCPKeepAlive yes" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "ClientAliveInterval 60" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;echo "ClientAliveCountMax 3" &amp;gt;&amp;gt; /etc/ssh/sshd_config &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;sed -i '/^AllowTcpForwarding no$/d' /etc/ssh/sshd_config&lt;/span&gt;


&lt;span class="c1"&gt;# Create SSH directory for root user and set permissions&lt;/span&gt;
&lt;span class="s"&gt;RUN mkdir -p /root/.ssh &amp;amp;&amp;amp; chmod 700 /root/.ssh&lt;/span&gt;

&lt;span class="s"&gt;RUN touch /root/.ssh/authorized_keys&lt;/span&gt;

&lt;span class="s"&gt;COPY authorized_keys /root/.ssh/authorized_keys&lt;/span&gt;
&lt;span class="s"&gt;RUN chmod 600 /root/.ssh/authorized_keys&lt;/span&gt;

&lt;span class="s"&gt;COPY nginx.conf /etc/nginx/nginx.conf&lt;/span&gt;

&lt;span class="c1"&gt;# Expose port 22 for SSH&lt;/span&gt;
&lt;span class="s"&gt;EXPOSE 22 &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;

&lt;span class="c1"&gt;# Start the SSH service&lt;/span&gt;
&lt;span class="s"&gt;CMD /usr/sbin/sshd &amp;amp;&amp;amp; nginx -g "daemon off;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Dockerfile sets up an OpenSSH server alongside NGINX and exposes two ports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Port 22: For SSH connections.&lt;/li&gt;
&lt;li&gt;Port 80: For NGINX HTTP traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;nginx.conf&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;

    server {
        listen 80;
        set_real_ip_from 0.0.0.0/0;

        server_name woovi-ssh;

        location / {
            proxy_pass http://localhost:5001;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration forwards all HTTP requests on port 80 to a local service running on port 5001. You’ll use this setup to forward traffic from your local machine to the remote server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forwarding Your Localhost to the Remote Server
&lt;/h2&gt;

&lt;p&gt;Once the Docker container is deployed, you can forward your local server to the remote server using an SSH remote port forward. Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -R 5001:localhost:5001 root@myowndomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command forwards requests from the remote server’s 5001 port to your local machine’s 5001 port. For example, accessing &lt;code&gt;http://myowndomain.com/anypath&lt;/code&gt; will route traffic to &lt;code&gt;http://localhost:5001/anypath&lt;/code&gt; on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;By combining NGINX, OpenSSH, and Docker, you’ve created a self-hosted alternative to ngrok. This solution gives you complete control over your traffic without relying on third-party services, making it ideal for privacy-conscious developers and teams with specific tunneling needs.&lt;/p&gt;

&lt;p&gt;Have you tried this setup? Let us know how it worked for you or share any questions in the comments!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://github.com/woovibr/jobs" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ngrok</category>
      <category>ssh</category>
      <category>nginx</category>
    </item>
    <item>
      <title>How to self-host any open source service?</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Mon, 27 Jan 2025 11:23:45 +0000</pubDate>
      <link>https://forem.com/woovi/how-to-self-host-any-open-source-service-1p8d</link>
      <guid>https://forem.com/woovi/how-to-self-host-any-open-source-service-1p8d</guid>
      <description>&lt;p&gt;Self-hosting an open-source service can provide greater control, privacy, and customization options than managed services. Kubernetes (K8s) is a powerful orchestration tool that simplifies deploying, scaling, and managing containerized applications.&lt;/p&gt;

&lt;p&gt;This is a practical of getting an open-source self-hosted service and deploying to a k8s cluster. We recommend searching and trying in this respective order:&lt;/p&gt;

&lt;h2&gt;
  
  
  Helm Chart
&lt;/h2&gt;

&lt;p&gt;Search for an existing Helm Chart for the service that you want to self-host. Search like: &lt;code&gt;&amp;lt;service&amp;gt; helm chart&lt;/code&gt;&lt;br&gt;
Helm Charts are like packages for k8s.&lt;br&gt;
You just need to setup a &lt;code&gt;values.yaml&lt;/code&gt; to customize to your own needs.&lt;/p&gt;

&lt;p&gt;Imagine that I want to self-host n8n service.&lt;br&gt;
I found the &lt;a href="https://github.com/8gears/n8n-helm-chart" rel="noopener noreferrer"&gt;n8n helm chart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You create a folder inside &lt;code&gt;deployment/n8n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and you create 2 files&lt;/p&gt;

&lt;p&gt;Chart.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v2&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci://8gears.container-registry.com/library&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.25.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;/p&gt;

&lt;p&gt;values.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresdb&lt;/span&gt;
    &lt;span class="na"&gt;postgresdb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pg.n8n&lt;/span&gt;
      &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;safepassword&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.73.1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can just run helm install to install your chart to a k8s cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;n8n ./deployments/n8n &lt;span class="nt"&gt;-f&lt;/span&gt; ./deployments/n8n/values.yaml &lt;span class="nt"&gt;-n&lt;/span&gt; n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could also use ArgoCD to sync it if you are using GitOps.&lt;br&gt;
Check how to setup ArgoCD in this article: &lt;a href="https://dev.to/woovi/bootstrapping-argocd-with-helm-and-helmfile-2fhe"&gt;Bootstrapping ArgoCD with Helm and Helmfile&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this didn't work in the first try, check pods, check logs, modify values.yaml, read the yamls of the helm chart to understand what is breaking&lt;/p&gt;
&lt;h2&gt;
  
  
  Pure Kubernetes setup
&lt;/h2&gt;

&lt;p&gt;If you didn't get luck to fine a good helm chart or any helm chart for the service you want to self host, try to search only by &lt;code&gt;&amp;lt;service&amp;gt; kubernetes&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you file, just copy the yamls files and modify them to your use case. It probably gonna have at least one deployment and one service file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Docker Compose
&lt;/h2&gt;

&lt;p&gt;If there is no resource about self hosting the service using kubernetes, let's fallback to docker compose. Search like this: &lt;code&gt;&amp;lt;service&amp;gt; docker compose&lt;/code&gt;.&lt;br&gt;
If you find a docker-compose.yaml file, read it to understand all the services that are required to run the service you want to self host.&lt;br&gt;
You can remove any optional service to simplify your setup, and add them back later on.&lt;br&gt;
I recommend having some shared resources like databases in a different deployment so you can reuse ithem n different self-hosted services. &lt;br&gt;
This makes self-hosting faster, easier, and cheaper.&lt;br&gt;
Do not reuse resources/services if this is a critical production service.&lt;/p&gt;

&lt;p&gt;You need to translate the docker-compose to k8s yamls, you can use chatgpt if you don't know how to translate, but it will be mostly some deployments and some services. Or try &lt;a href="https://kompose.io/" rel="noopener noreferrer"&gt;Kompose.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is an example of deployment of n8n based on a docker-compose.yaml&lt;/p&gt;

&lt;p&gt;n8n-deployment.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-server&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RollingUpdate&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-server&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-server&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-server&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.n8n.io/n8nio/n8n:latest&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100m&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-secrets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;n8n-service.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  namespace: n8n
  name: n8n-server
spec:
  selector:
    app: n8n-server
  ports:
    - protocol: TCP
      port: 5678
      targetPort: 5678
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: n8n-secrets
  namespace: n8n
stringData:
   N8N_HOST: "n8n.dev.to"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deployment will run a number of instances of n8n, and service will enable us to expose to a nginx/ingress or loadbalancer.&lt;br&gt;
Keep all secrets in a secret config or use a vault solution.&lt;br&gt;
You can also self-host a vault solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;Follow the same steps as docker compose, and transform a single docker image in 1 deployment and 1 service like above changing the image to your service docker image.&lt;/p&gt;

&lt;h2&gt;
  
  
  None of the above
&lt;/h2&gt;

&lt;p&gt;If your service does not even have a docker image, you can create a Dockerfile from scratch to create an image.&lt;br&gt;
You can host this image in any docker registry.&lt;br&gt;
You can also self-host the docker registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By following these steps, you can self-host any open-source service using Kubernetes, giving you full control and flexibility. While Kubernetes has a learning curve, the long-term benefits of scalability, reliability, and customization make it worth the effort&lt;/p&gt;

&lt;p&gt;What services does not fit any of the above?&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://github.com/woovibr/jobs" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>opensource</category>
      <category>kubernetes</category>
      <category>docker</category>
    </item>
    <item>
      <title>Bootstrapping ArgoCD with Helm and Helmfile</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Fri, 24 Jan 2025 10:48:01 +0000</pubDate>
      <link>https://forem.com/woovi/bootstrapping-argocd-with-helm-and-helmfile-2fhe</link>
      <guid>https://forem.com/woovi/bootstrapping-argocd-with-helm-and-helmfile-2fhe</guid>
      <description>&lt;h2&gt;
  
  
  Bootstrapping ArgoCD with Helm and Helmfile
&lt;/h2&gt;

&lt;p&gt;ArgoCD is a powerful tool for managing GitOps workflows in Kubernetes. Combining it with Helm and Helmfile streamlines the setup process, enabling version-controlled configurations and better automation.&lt;/p&gt;

&lt;p&gt;In this article, we’ll walk through setting up ArgoCD using Helm and managing it with Helmfile for an efficient and maintainable deployment process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Helm and Helmfile for ArgoCD?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Helm simplifies application packaging and deployment in Kubernetes.&lt;/li&gt;
&lt;li&gt;Helmfile extends Helm by managing multiple Helm charts and their configurations declaratively.&lt;/li&gt;
&lt;li&gt;Combining Helmfile and Helm for ArgoCD ensures consistent and reproducible setups, especially in environments with multiple clusters or teams.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting, ensure the following are in place:&lt;/p&gt;

&lt;p&gt;A Kubernetes cluster.&lt;br&gt;
Helm installed (helm version).&lt;br&gt;
Helmfile installed (helmfile version).&lt;br&gt;
A Git repository to store your Helmfile and values files.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Add the ArgoCD Helm Repository
&lt;/h2&gt;

&lt;p&gt;First, add the official ArgoCD Helm repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 2: Create a helmfile.yaml
&lt;/h1&gt;

&lt;p&gt;Create a helmfile.yaml to manage the ArgoCD Helm chart. Here’s a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argo&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://argoproj.github.io/argo-helm&lt;/span&gt;

&lt;span class="na"&gt;releases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
    &lt;span class="na"&gt;createNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argo/argo-cd&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~5.46.0&lt;/span&gt;  &lt;span class="c1"&gt;# Specify the desired version&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;values/argocd-values.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Define Values for ArgoCD
&lt;/h2&gt;

&lt;p&gt;In the values/argocd-values.yaml file, customize the ArgoCD configuration. Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;argo-cd:
  dex:
    enabled: false
  notifications:
    enabled: false
  applicationSet:
    enabled: false
  server:
    extraArgs:
      - --insecure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adjust these values based on your environment, such as enabling ingress or setting up replicas for HA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Deploy ArgoCD with Helmfile
&lt;/h2&gt;

&lt;p&gt;Run the following command to deploy ArgoCD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helmfile apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will:&lt;/p&gt;

&lt;p&gt;Create the &lt;code&gt;argocd&lt;/code&gt; namespace (if not already existing).&lt;br&gt;
Install or upgrade the ArgoCD Helm chart using the specified values.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Verify the Installation
&lt;/h2&gt;

&lt;p&gt;Check the status of the deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; argocd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access the ArgoCD UI (replace LoadBalancer or ingress domain accordingly):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/argocd-server &lt;span class="nt"&gt;-n&lt;/span&gt; argocd 8080:443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default username is admin, and the password can be fetched using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get secret argocd-initial-admin-secret &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{.data.password}"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Manage Configuration with GitOps
&lt;/h2&gt;

&lt;p&gt;Now that ArgoCD is set up, you can manage your Kubernetes applications declaratively using Git repositories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages of Using Helmfile for ArgoCD
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Centralized Configurations: All charts and values are in one file, making it easy to track changes.&lt;/li&gt;
&lt;li&gt;Reproducibility: Deployments are consistent across environments.&lt;/li&gt;
&lt;li&gt;Multi-environment Support: Define multiple environments (e.g., dev, staging, production) in a single Helmfile.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Bootstrapping ArgoCD with Helm and Helmfile is a powerful way to simplify and streamline GitOps workflows. With the declarative nature of Helmfile, you can easily manage and version-control your ArgoCD setup, ensuring smooth operations across your Kubernetes clusters.&lt;/p&gt;

&lt;p&gt;Feel free to extend this setup further by incorporating secrets management tools, custom plugins, or CI/CD pipelines for automated deployments.&lt;/p&gt;

&lt;p&gt;Did you find this guide helpful? Share your thoughts or tips for improving the ArgoCD setup using Helm and Helmfile in the comments!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://github.com/woovibr/jobs" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>argocd</category>
      <category>helm</category>
      <category>helmfile</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Sharing a Redis using a Prefix</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Thu, 23 Jan 2025 11:07:24 +0000</pubDate>
      <link>https://forem.com/woovi/sharing-a-redis-using-a-prefix-43e2</link>
      <guid>https://forem.com/woovi/sharing-a-redis-using-a-prefix-43e2</guid>
      <description>&lt;h2&gt;
  
  
  Using Bull Prefix to Isolate Jobs Between Developers
&lt;/h2&gt;

&lt;p&gt;When developers share resources from staging to develop locally, issues can arise, particularly when using shared Redis instances and Bull for distributed jobs. For instance, one developer might unintentionally consume jobs created by another, leading to conflicts and confusion.&lt;/p&gt;

&lt;p&gt;To learn more about the benefits of leveraging staging services in development, check out this detailed guide: &lt;a href="https://dev.to/woovi/enhancing-dx-by-using-staging-services-in-development-3f0d"&gt;Enhancing DX by Using Staging Services in Development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One effective solution to this problem is using job prefixing. This strategy allows each developer to have their namespace or prefix for jobs, ensuring that jobs created by one developer are only processed by their workers. Here's how you can implement this:&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Job Prefixes?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prevent Job Conflicts: Multiple developers may add jobs to the same queue without realizing it, leading to a mix-up of jobs.&lt;/li&gt;
&lt;li&gt;Isolation of Developer Work: Each developer can have a unique prefix that only their jobs can be consumed by their workers.&lt;/li&gt;
&lt;li&gt;Scalability: As the project grows and more developers work with queues, job prefixes help keep things organized.&lt;/li&gt;
&lt;li&gt;Cost-Effective: Eliminates the need to spin up a new Redis instance for every developer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using Bull Prefixing for Job Isolation
&lt;/h2&gt;

&lt;p&gt;Bull, a popular queue system for Node.js, supports prefixes for queue and job names, allowing you to easily create isolated queues for each developer.&lt;/p&gt;

&lt;p&gt;By leveraging this feature, you can ensure that developers working in parallel don’t interfere with one another. Here’s how you can implement this approach:&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Prefixing with BULL_DEV_PREFIX
&lt;/h2&gt;

&lt;p&gt;Using process.env as feature flags is a common practice for enabling or disabling experimental or development-only features. If you're unfamiliar with this approach, check out this guide: &lt;a href="https://dev.to/woovi/processenv-as-feature-flags-nf"&gt;Using process.env as Feature Flags&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To simplify the configuration for developers, you can introduce an environment variable (BULL_DEV_PREFIX) in their local .env file. This prefix ensures that all jobs created by a developer will only be consumed by their workers. If no prefix is set, jobs will remain accessible to others, including staging.&lt;/p&gt;

&lt;p&gt;This setup is particularly useful when testing new job implementations without affecting shared queues.&lt;/p&gt;

&lt;p&gt;Example Implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getDevPrefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BULL_DEV_PREFIX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BULL_DEV_PREFIX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MY_QUEUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;getDevPrefix&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;    
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;BULL_DEV_PREFIX&lt;/code&gt; variable is read from the environment.&lt;br&gt;
If set, the prefix is applied to the queue, ensuring isolation.&lt;br&gt;
If not set, the queue behaves normally, allowing staging and other developers to interact with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagram with before and after the solution
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjphc6i27xjjx2sam5c1d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjphc6i27xjjx2sam5c1d.png" alt="before-and-after" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using Bull prefixes is a simple and effective way to improve collaboration in a multi-developer environment. It ensures:&lt;/p&gt;

&lt;p&gt;Developers can work independently on shared queues.&lt;br&gt;
Conflicts are avoided.&lt;br&gt;
Staging resources are utilized efficiently.&lt;br&gt;
This approach boosts workflow efficiency and scalability, allowing teams to focus on development without stepping on each other’s toes.&lt;/p&gt;




&lt;p&gt;Woovi&lt;br&gt;
&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://github.com/woovibr/jobs" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.pexels.com/photo/siblings-playing-a-green-plush-toys-3662845/" rel="noopener noreferrer"&gt;Photo by cottonbro studio&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bull</category>
      <category>prefix</category>
      <category>scaling</category>
      <category>redis</category>
    </item>
    <item>
      <title>Enhancing DX by Using Staging Services in Development</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Wed, 22 Jan 2025 11:20:39 +0000</pubDate>
      <link>https://forem.com/woovi/enhancing-dx-by-using-staging-services-in-development-3f0d</link>
      <guid>https://forem.com/woovi/enhancing-dx-by-using-staging-services-in-development-3f0d</guid>
      <description>&lt;h1&gt;
  
  
  Complexity as You Grow
&lt;/h1&gt;

&lt;p&gt;When starting with the MVP (Minimal Viable Product) of your startup, your repository is small, typically with one backend and one frontend. Initially, you'll have limited code files, endpoints, pages, dependencies, and services. However, as your startup grows, so will everything—files, endpoints, pages, dependencies, and services. This growth can slow down your compilation, bundling, and feedback loop during development, and increase CPU and memory usage as external services accumulate.&lt;/p&gt;

&lt;h1&gt;
  
  
  Staging for Backend Services
&lt;/h1&gt;

&lt;p&gt;A key optimization is to move all services to a staging environment and have all developers use the same services locally—like MongoDB, Elasticsearch, Redis, and others. This ensures consistency while reducing local overhead.&lt;/p&gt;

&lt;h1&gt;
  
  
  Isolating Work for Specific Endpoints
&lt;/h1&gt;

&lt;p&gt;Rather than running the entire application, developers typically focus on specific endpoints or services. Why not let them work on just those parts without the need to run the whole project? Here's an example of how to achieve this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getDevKoaApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Koa&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Koa&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// we are behind a proxy&lt;/span&gt;
    &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JWT_KEY&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// used for cookies sign&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// add koa middleware&lt;/span&gt;

  &lt;span class="c1"&gt;// use router that will be used to run local code&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allowedMethods&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="c1"&gt;// proxy all the other requests to staging&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerStagingProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerStagingProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allowedMethods&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getDevKoaApp&lt;/code&gt; receives a router with only the endpoints the developer is working on. &lt;br&gt;
&lt;code&gt;routerStagingProxy&lt;/code&gt; forwards any requests not handled locally to the staging environment.&lt;/p&gt;

&lt;p&gt;Here's the implementation of &lt;code&gt;routerStagingProxy&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;routerStagingProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/(.*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;staging&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// eslint-disable-next-line&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proxying to staging: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;operationName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// eslint-disable-next-line&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;operationName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;koaProxy2&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;staging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;suppressRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;forbiddenHeaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;changeOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;onProxyReq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxyReq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;forbiddenHeaders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;proxyReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup forwards all endpoints that aren’t running locally to staging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Staging for Frontend Services
&lt;/h2&gt;

&lt;p&gt;For frontend services, you can just define the &lt;code&gt;apiBaseUrl&lt;/code&gt; that will forward the request from local to staging&lt;/p&gt;

&lt;p&gt;We are using Rspack for his.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;devServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/console&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;changeOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cookieDomainRewrite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onProxyReq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxyReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Browers may send Origin headers even with same-origin&lt;/span&gt;
      &lt;span class="c1"&gt;// requests. To prevent CORS issues, we have to change&lt;/span&gt;
      &lt;span class="c1"&gt;// the Origin to match the target URL.&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxyReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;proxyReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Forward Services to a Staging Environment?
&lt;/h2&gt;

&lt;p&gt;Developing locally against a staging environment has several advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Access to Real Data:&lt;/strong&gt; You can test features with near-production data without replicating databases or APIs locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Environment:&lt;/strong&gt; Avoid issues where "it works on my machine" but fails in staging due to environment differences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster Iterations:&lt;/strong&gt; Skip the setup time for heavy services locally, focusing instead on core application logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;As your product scales, adding more services, endpoints, and frontends can overwhelm your developers. Forwarding services from local to staging provides a seamless developer experience—developers can work locally as if they’re running everything, without the overhead of managing all services.&lt;/p&gt;

&lt;p&gt;How are you scaling your codebase as your product grows? Let me know!&lt;/p&gt;




&lt;p&gt;Woovi&lt;br&gt;
&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://github.com/woovibr/jobs" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;




&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@dulhiier?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Nastya Dulhiier&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/lighted-city-at-night-aerial-photo-OKOOGO578eo?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dx</category>
      <category>staging</category>
    </item>
    <item>
      <title>Optimizing SSH Connections</title>
      <dc:creator>Sibelius Seraphini</dc:creator>
      <pubDate>Tue, 21 Jan 2025 12:05:28 +0000</pubDate>
      <link>https://forem.com/woovi/optimizing-ssh-connections-1oa4</link>
      <guid>https://forem.com/woovi/optimizing-ssh-connections-1oa4</guid>
      <description>&lt;p&gt;As we move to &lt;a href="https://dev.to/woovi/remote-development-at-woovi-25mj"&gt;remote development&lt;/a&gt;, we need to optimize SSH connections.&lt;br&gt;
SSH (Secure Shell) is an essential tool for securely managing remote systems. However, its performance and usability can be significantly improved with a few tweaks. Here's how:&lt;/p&gt;
&lt;h2&gt;
  
  
  Using an SSH Config File
&lt;/h2&gt;

&lt;p&gt;The SSH config file (~/.ssh/config) allows you to simplify and optimize your SSH connections. Instead of repeatedly typing lengthy commands, you can define settings for each host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host myserver
  HostName 192.168.1.100
  User myuser
  Port 22
  IdentityFile ~/.ssh/id_rsa
  Compression yes
  ServerAliveInterval 60
  ServerAliveCountMax 3
  LogLevel QUIET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;br&gt;
Ease of Use: Simplify commands with aliases (e.g., ssh myserver).&lt;br&gt;
Performance: Enable compression for faster transfers, especially over slow connections.&lt;br&gt;
Stability: Avoid disconnections with ServerAliveInterval and ServerAliveCountMax&lt;/p&gt;
&lt;h2&gt;
  
  
  SSH Multiplexing
&lt;/h2&gt;

&lt;p&gt;Multiplexing reuses existing SSH connections for multiple sessions, reducing authentication overhead and improving speed for subsequent logins.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host *
  ControlMaster auto
  ControlPath ~/.ssh/cm-%r@%h:%p
  ControlPersist 10m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;br&gt;
ControlMaster auto: Enables multiplexing.&lt;br&gt;
ControlPath: Defines a socket file for reusing connections.&lt;br&gt;
ControlPersist 10m: Keeps the connection open for 10 minutes after the session ends.&lt;/p&gt;
&lt;h2&gt;
  
  
  Automating LocalForward and RemoteForward
&lt;/h2&gt;

&lt;p&gt;Port forwarding is a powerful SSH feature for securely tunneling traffic. Automating it in the config file ensures consistency and saves time.&lt;/p&gt;

&lt;p&gt;LocalForward:&lt;br&gt;
Use LocalForward to forward a local port to a remote service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host myserver
  LocalForward 8080 127.0.0.1:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access a remote web server running on port 80 via localhost:8080.&lt;br&gt;
RemoteForward:&lt;br&gt;
Use RemoteForward to expose a local service to the remote host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host myserver
  RemoteForward 9090 127.0.0.1:3306
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expose a local MySQL instance running on port 3306 to the remote host's port 9090.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using AutoSSH for Persistent Connections
&lt;/h2&gt;

&lt;p&gt;AutoSSH is a utility that automatically restarts SSH sessions if they get disconnected, ensuring a persistent, fault-tolerant connection. It’s particularly useful for remote systems that require continuous monitoring or tunneling, like when you are maintaining a VPN-like setup using SSH tunnels or monitoring remote systems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;autossh -M 0 -f -N -T -L 8080:localhost:80 myserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with SSH config&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host myserver
  HostName 192.168.1.100
  User myuser
  IdentityFile ~/.ssh/id_rsa
  LocalForward 8080 127.0.0.1:80
  RemoteForward 9090 127.0.0.1:3306
  ControlMaster auto
  ControlPath ~/.ssh/cm-%r@%h:%p
  ControlPersist 10m
  ProxyCommand autossh -M 0 -f -N -T -L 8080:localhost:80 %h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By leveraging SSH configuration files, enabling multiplexing, and automating forwarding options, you can significantly enhance your SSH workflow. These tweaks reduce repetitive tasks, improve connection speed, and make remote management seamless.&lt;/p&gt;

&lt;p&gt;Optimize your setup today and enjoy faster, more efficient SSH connections!&lt;/p&gt;




&lt;p&gt;Woovi&lt;br&gt;
&lt;a href="https://www.woovi.com" rel="noopener noreferrer"&gt;Woovi&lt;/a&gt; is a fintech platform revolutionizing how businesses and developers handle payments in Brazil. Built with a developer-first mindset, Woovi simplifies integration with instant payment methods like Pix, enabling companies to receive payments seamlessly and automate financial workflows.&lt;/p&gt;

&lt;p&gt;If you want to work with us, we are &lt;a href="https://github.com/woovibr/jobs" rel="noopener noreferrer"&gt;hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>remote</category>
      <category>woovi</category>
    </item>
  </channel>
</rss>
