<?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>DEV Community: Alexandre Fernandes dos Santos</title>
    <description>The latest articles on DEV Community by Alexandre Fernandes dos Santos (@xandecodes).</description>
    <link>https://dev.to/xandecodes</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%2F354814%2F8a20727e-b070-4b04-834b-53a7e8e85ed5.jpeg</url>
      <title>DEV Community: Alexandre Fernandes dos Santos</title>
      <link>https://dev.to/xandecodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xandecodes"/>
    <language>en</language>
    <item>
      <title>Docker Compose - Observability made easy</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Sat, 14 Feb 2026 21:52:25 +0000</pubDate>
      <link>https://dev.to/xandecodes/docker-compose-observability-made-easy-3030</link>
      <guid>https://dev.to/xandecodes/docker-compose-observability-made-easy-3030</guid>
      <description>&lt;p&gt;Hello, JavaScript backend veterans! I bring you an observability stack based on OpenTelemetry, using VictoriaMetrics for metrics, VictoriaLogs for logs, and Jaeger for traces.&lt;/p&gt;

&lt;p&gt;It's a stack focused on a development environment, but nothing prevents it from being used in a low-volume production application with some adjustments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;p&gt;This time I couldn't bring just a single Docker Compose YAML 😢, but it's only a little bit more, I promise 🙏.&lt;/p&gt;

&lt;h3&gt;
  
  
  otel_config.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;:4317&lt;/span&gt;

&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jaeger:4317&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;insecure&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;otlphttp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs_endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://victoria_logs:9428/insert/opentelemetry/v1/logs&lt;/span&gt;
    &lt;span class="na"&gt;metrics_endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://victoria_metrics:8428/opentelemetry/v1/metrics&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;insecure&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;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlphttp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlphttp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is responsible for the OpenTelemetry Collector configuration.&lt;/p&gt;

&lt;p&gt;It basically defines how we will receive our application data (receivers), allows us to add some processing (like compression and batching) if we want, and configures how data will be exported (exporters).&lt;/p&gt;

&lt;h3&gt;
  
  
  datasources.yaml
&lt;/h3&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="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;datasources&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;VictoriaMetrics&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;victoriametrics-metrics-datasource&lt;/span&gt;
    &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&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;http://victoria_metrics:8428&lt;/span&gt;
    &lt;span class="na"&gt;isDefault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;VictoriaLogs&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;victoriametrics-logs-datasource&lt;/span&gt;
    &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&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;http://victoria_logs:9428&lt;/span&gt;
    &lt;span class="na"&gt;isDefault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need this file to configure the Grafana datasources—in our case, the metrics and logs sources that will be used.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker-compose.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grafana/grafana:12.3.3"&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="s"&gt;grafana:/var/lib/grafana&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:ro&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&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="s"&gt;3000:3000&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GF_PLUGINS_PREINSTALL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;victoriametrics-metrics-datasource,victoriametrics-logs-datasource&lt;/span&gt;
      &lt;span class="na"&gt;GF_SECURITY_ADMIN_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
      &lt;span class="na"&gt;GF_SECURITY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;

  &lt;span class="na"&gt;otlp_collector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;otel/opentelemetry-collector-contrib:0.145.0"&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="s"&gt;./otel_config.yaml:/etc/otelcol/config.yaml:ro&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--config=/etc/otelcol/config.yaml"&lt;/span&gt;&lt;span class="pi"&gt;]&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="s"&gt;4317:4317&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt; 

  &lt;span class="na"&gt;victoria_metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;victoriametrics/victoria-metrics:v1.135.0"&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="s"&gt;metrics:/victoria-metrics-data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storageDataPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;victoria-metrics-data"&lt;/span&gt;
      &lt;span class="na"&gt;retentionPeriod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1d"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt; 

  &lt;span class="na"&gt;victoria_logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;victoriametrics/victoria-logs:v1.45.0"&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="s"&gt;logs:/victoria-metrics-data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storageDataPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;victoria-metrics-data"&lt;/span&gt;
      &lt;span class="na"&gt;retentionPeriod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1d"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt; 

  &lt;span class="na"&gt;jaeger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cr.jaegertracing.io/jaegertracing/jaeger:2.15.0"&lt;/span&gt; 
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&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="s"&gt;16686:16686&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MEMORY_MAX_TRACES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is where the fun begins. I don't think it's worth going into too much detail because everything can be found better explained elsewhere than a mere mortal like me could do, so I'll just leave a few points of attention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Logs and metrics will be exposed by Grafana on port 3000. So, by connecting to localhost:3000 and using the credentials set in the YAML, you'll have access to them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Traces will be exposed similarly, but the Jaeger tool uses port 16686.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This Jaeger instance was configured without persistence, so upon restarting the container, previously ingested traces will be lost. In case you want an easy way to add persistence, you can use &lt;a href="https://www.jaegertracing.io/docs/2.15/storage/badger/" rel="noopener noreferrer"&gt;Badger&lt;/a&gt; as a Storage Backend.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Arrivederci
&lt;/h3&gt;

&lt;p&gt;Until later, my friends! I hope this article is useful in your journey to spin up OTLP locally.&lt;/p&gt;

&lt;p&gt;That's all! In case you have any doubts or suggestions, just let me know. I'll be happy to answer!&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>monitoring</category>
      <category>observability</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Docker Compose - Stack de Observabilidade sem complicação</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Sat, 14 Feb 2026 21:20:09 +0000</pubDate>
      <link>https://dev.to/xandecodes/docker-compose-stack-de-observabilidade-sem-complicacao-2430</link>
      <guid>https://dev.to/xandecodes/docker-compose-stack-de-observabilidade-sem-complicacao-2430</guid>
      <description>&lt;p&gt;Olá, veteranos do JavaScript no backend! Trago para vocês uma stack de observabilidade baseada em OpenTelemetry, usando VictoriaMetrics para métricas, VictoriaLogs para logs e Jaeger para Traces.&lt;/p&gt;

&lt;p&gt;É uma stack focada em ambiente de desenvolvimento, mas nada impede que, com alguns ajustes, possa ser usada em uma aplicação em produção de baixo volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;p&gt;Dessa vez não vai dar para ser só um YAML de docker compose 😢, mas é pouca coisa a mais, prometo 🙏.&lt;/p&gt;

&lt;h3&gt;
  
  
  otel_config.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;:4317&lt;/span&gt;

&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jaeger:4317&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;insecure&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;otlphttp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs_endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://victoria_logs:9428/insert/opentelemetry/v1/logs&lt;/span&gt;
    &lt;span class="na"&gt;metrics_endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://victoria_metrics:8428/opentelemetry/v1/metrics&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;insecure&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;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlphttp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlphttp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse arquivo é responsável pela configuração do Collector do OpenTelemetry. &lt;/p&gt;

&lt;p&gt;Ele basicamente define como devemos receber os dados da nossa aplicação (receivers), adiciona alguns processamentos (como compressão e batch) se quisermos, e configura para onde os dados serão exportados (exporters).&lt;/p&gt;

&lt;h3&gt;
  
  
  datasources.yaml
&lt;/h3&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="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;datasources&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;VictoriaMetrics&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;victoriametrics-metrics-datasource&lt;/span&gt;
    &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&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;http://victoria_metrics:8428&lt;/span&gt;
    &lt;span class="na"&gt;isDefault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;VictoriaLogs&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;victoriametrics-logs-datasource&lt;/span&gt;
    &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&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;http://victoria_logs:9428&lt;/span&gt;
    &lt;span class="na"&gt;isDefault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Precisamos desse arquivo para configurarmos a fontes de informações do grafana, no caso as fontes de métricas e logs que vão ser utilizadas.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker-compose.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grafana/grafana:12.3.3"&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="s"&gt;grafana:/var/lib/grafana&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:ro&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&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="s"&gt;3000:3000&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GF_PLUGINS_PREINSTALL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;victoriametrics-metrics-datasource,victoriametrics-logs-datasource&lt;/span&gt;
      &lt;span class="na"&gt;GF_SECURITY_ADMIN_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
      &lt;span class="na"&gt;GF_SECURITY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;

  &lt;span class="na"&gt;otlp_collector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;otel/opentelemetry-collector-contrib:0.145.0"&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="s"&gt;./otel_config.yaml:/etc/otelcol/config.yaml:ro&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--config=/etc/otelcol/config.yaml"&lt;/span&gt;&lt;span class="pi"&gt;]&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="s"&gt;4317:4317&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt; 

  &lt;span class="na"&gt;victoria_metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;victoriametrics/victoria-metrics:v1.135.0"&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="s"&gt;metrics:/victoria-metrics-data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storageDataPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;victoria-metrics-data"&lt;/span&gt;
      &lt;span class="na"&gt;retentionPeriod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1d"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt; 

  &lt;span class="na"&gt;victoria_logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;victoriametrics/victoria-logs:v1.45.0"&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="s"&gt;logs:/victoria-metrics-data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storageDataPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;victoria-metrics-data"&lt;/span&gt;
      &lt;span class="na"&gt;retentionPeriod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1d"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt; 

  &lt;span class="na"&gt;jaeger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cr.jaegertracing.io/jaegertracing/jaeger:2.15.0"&lt;/span&gt; 
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&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="s"&gt;16686:16686&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MEMORY_MAX_TRACES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui é onde a brincadeira começa, não acho que vale muito entrar em detalhes porque tudo pode ser achado de uma forma muito melhor explicada que um mero mortal como eu faria, então vou só deixar alguns pontos de atenção.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Logs e métricas vão ser expostas pelo grafana que está na porta 3000, logo entrando em localhost:3000 e colocando as senhas que estão explicitadas no yaml, você terá acesso a eles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Traces vão estar expostos de forma parecida mas na ferramenta do Jaeger na porta 16686.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O Jaeger foi configurado sem persistência, então ao reiniciar o container os traces ingeridos vão ser perdidos, caso queira uma forma fácil de adicionar persistência pode usar o &lt;a href="https://www.jaegertracing.io/docs/2.15/storage/badger/" rel="noopener noreferrer"&gt;Badger&lt;/a&gt; como Storage Backend.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Arrivederci
&lt;/h3&gt;

&lt;p&gt;Até mais meus caros espero ter sido útil na jornada de subir um oltp localmente.&lt;/p&gt;

&lt;p&gt;É isso, caso tenham alguma dúvida ou sugestão basta me avisar. Ficarei feliz em responder!.&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>monitoring</category>
      <category>observability</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Docker Compose - SFTP: Managing files securely</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Sun, 25 Jan 2026 21:29:40 +0000</pubDate>
      <link>https://dev.to/xandecodes/docker-compose-sftp-managing-files-securely-48b0</link>
      <guid>https://dev.to/xandecodes/docker-compose-sftp-managing-files-securely-48b0</guid>
      <description>&lt;p&gt;Good morning everyone! I hope this finds you well. Today I want to share with you almost a version 2.0 of my old article &lt;a href=""&gt;Docker Compose - Server FTP&lt;/a&gt;. This text was born from my desire to bring some security improvements and best practice tips.&lt;/p&gt;

&lt;p&gt;Don't get me wrong, the previous text has its charm. If you want to spin up a basic and fast FTP server, that's your go-to.&lt;/p&gt;

&lt;p&gt;Well, in case you don't know the &lt;strong&gt;SFTP&lt;/strong&gt; protocol (&lt;em&gt;Secure File Transfer Protocol&lt;/em&gt; or &lt;em&gt;SSH File Transfer Protocol&lt;/em&gt;), it is a network file transfer protocol like FTP, but it uses SSH to encrypt commands and data (during transmission).&lt;/p&gt;

&lt;h2&gt;
  
  
  Who can, can; who can't, won't
&lt;/h2&gt;

&lt;p&gt;I advise creating a directory with two subdirectories: one for files and another to persist credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sftp
|-- files
|-- creds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To avoid permission problems, we should guarantee the correct access rights to prevent issues when erasing or editing files created by the Docker container.&lt;/p&gt;

&lt;p&gt;To do this, I advise finding out which user will be used and adding the directories to that user's group, as well as setting permissions via &lt;code&gt;chmod&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Note: If the user who created the directories is the same one who will execute the docker container, this step might not be necessary, though it is recommended.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For simplicity's sake, I'll assume that the user being used in the shell is the same one running Docker, so let's go!&lt;/p&gt;

&lt;p&gt;1 - Discover the current user using &lt;code&gt;whoami&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ whoami
alexandre
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 - In my case, the user is &lt;em&gt;alexandre&lt;/em&gt;. Now let's check the user's ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ id
uid=1000(alexandre) gid=1000(alexandre) grupos=1000(alexandre),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),116(lpadmin),987(docker)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3 - Do you see the &lt;code&gt;uid&lt;/code&gt; value preceding our username in parentheses? The value inside it is what we want. In my case, it's &lt;em&gt;1000&lt;/em&gt;. We'll need this when we create the users with access to SFTP.&lt;/p&gt;

&lt;p&gt;4 - Add the necessary directories to the group using the &lt;em&gt;chown&lt;/em&gt; command on the folder we are working in, with the user and group in the following format: &lt;em&gt;user:group&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chown -R alexandre:alexandre ./sftp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5 - Adding permissions: the creator of the file and the group can &lt;em&gt;read&lt;/em&gt;, &lt;em&gt;edit&lt;/em&gt;, and &lt;em&gt;delete&lt;/em&gt; data, while others can only execute read operations (774).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod 774 -R ./sftp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the users file
&lt;/h2&gt;

&lt;p&gt;The application we are using today allows users to be passed via a file. I think this method is easier to manage than via command line or env vars, so let's create this file.&lt;/p&gt;

&lt;p&gt;Just create a file named &lt;em&gt;users.conf&lt;/em&gt; and add the user information in the following format &lt;code&gt;username:password:uid&lt;/code&gt;. I will explain each field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;username:&lt;/strong&gt; Can be any username, including your current user (my case).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;password:&lt;/strong&gt; I will leave it blank because we'll use an &lt;em&gt;SSH&lt;/em&gt; key to authenticate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;uid:&lt;/strong&gt; The UID we got in the previous step &lt;em&gt;(if you're in a hurry, try 1000 or 1001)&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My &lt;strong&gt;users.conf&lt;/strong&gt; content will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alexandre::1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generating SSH keys
&lt;/h2&gt;

&lt;p&gt;To generate the keys, I will use &lt;em&gt;ssh-keygen&lt;/em&gt;, which usually comes installed on Ubuntu.&lt;/p&gt;

&lt;p&gt;1 - Enter the &lt;em&gt;creds&lt;/em&gt; directory inside &lt;em&gt;/sftp&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;2 - Use the command &lt;strong&gt;ssh-keygen -t rsa&lt;/strong&gt; and type the filename. It can be any name; I used &lt;em&gt;key&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;3 - After that, it asks if you want to add a passphrase. It's optional. I usually don't set one, remembering that if added, it'll be requested upon every access.&lt;/p&gt;

&lt;h2&gt;
  
  
  After the Odyssey, Ulysses returns home
&lt;/h2&gt;

&lt;p&gt;Now for the best part. Here is the docker compose of happiness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  sftp:
    image: atmoz/sftp
    restart: 'unless-stopped' 
    volumes:
        - ./files:/home/alexandre/upload                          # The files will be maintained here
        - ./creds/key.pub:/home/alexandre/.ssh/keys/key.pub:ro    # Sharing keys with the service
        - ./users.conf:/etc/sftp/users.conf:ro                    # Adding users
    ports:
        - 2222:22                                                 # The exposed port will be 2222
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to spin it up, you just need to execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to connect
&lt;/h2&gt;

&lt;p&gt;If you want a tip for a client to connect to your freshly created service, I recommend the classic FileZilla. See below how to use it:&lt;/p&gt;

&lt;p&gt;1 - Open the &lt;em&gt;Site Manager&lt;/em&gt; in the File tab:&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%2Fewm782fkbyn5ez2prpw4.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%2Fewm782fkbyn5ez2prpw4.png" alt="file/site manager" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2 - Follow the suggested config:&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%2F82vnhmlwce6fb74b8sp4.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%2F82vnhmlwce6fb74b8sp4.png" alt="site manager/config" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocol:&lt;/strong&gt; SFTP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host:&lt;/strong&gt; localhost&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port:&lt;/strong&gt; 2222 - Normally port 22 is used, but I redirected it to 2222 to avoid conflicts with other ports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logon Type:&lt;/strong&gt; Key file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User:&lt;/strong&gt; alexandre - Add the user from the &lt;em&gt;users.conf&lt;/em&gt; file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key file:&lt;/strong&gt; Use the private key (without .pub) we created in the step [Generating SSH keys].&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background color:&lt;/strong&gt; It depends on your taste.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3 - Voilá! Now just enjoy making your dubious deployments or keeping your "green steam" files on your favorite SFTP (a father's and mother's love knows no bounds).&lt;/p&gt;

&lt;h2&gt;
  
  
  That's all, folks!
&lt;/h2&gt;

&lt;p&gt;I hope this text is useful for you. It became a little longer than I expected, but I wanted to explain everything thoroughly (I hope I achieved that 😅).&lt;/p&gt;

&lt;p&gt;However, if you have any questions, just leave a comment below and I'll be happy to answer them!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>security</category>
      <category>beginners</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Docker Compose - SFTP gerenciando arquivos com segurança</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Wed, 21 Jan 2026 00:54:13 +0000</pubDate>
      <link>https://dev.to/xandecodes/docker-compose-sftp-gerenciando-arquivos-com-seguranca-3m6j</link>
      <guid>https://dev.to/xandecodes/docker-compose-sftp-gerenciando-arquivos-com-seguranca-3m6j</guid>
      <description>&lt;p&gt;Bom dia a todos! Espero que tenha os encontrado bem. Hoje gostaria de compartilhar com vocês quase uma versão 2.0 do meu antigo texto de &lt;a href="https://dev.to/xandecodes/docker-compose-servidor-ftp-37n"&gt;Docker Compose - Servidor FTP&lt;/a&gt;, comigo querendo trazer algumas melhorias de segurança e dicas de boas práticas esse texto surgiu.&lt;/p&gt;

&lt;p&gt;Não me entenda mal, o texto anterior ele tem seu charme, se você quer subir um FTP básico e rápido ele é sua pedida.&lt;/p&gt;

&lt;p&gt;Bem caso não conheça o protocolo &lt;strong&gt;SFTP&lt;/strong&gt; (&lt;em&gt;Secure File Transfer Protocol ou SSH File Transfer Protocol&lt;/em&gt;)ele é um protocolo de transferência de arquivos pela rede como o FTP mas utiliza o SSH para criptografar os comandos e dados (durante a transmissão).&lt;/p&gt;

&lt;h2&gt;
  
  
  Quem pode, pode, quem não pode não vai estar podendo
&lt;/h2&gt;

&lt;p&gt;Aconselho a criar uma pasta com dois subdiretórios, um de arquivos (files) e outro para persistir as credenciais creds).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sftp
|-- files
|-- creds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E para não termos problemas de permissionamento é bom já garantirmos os acessos a quem deve, para evitarmos problemas ao apagar ou editar arquivos criados pelo container Docker.&lt;/p&gt;

&lt;p&gt;Para evitar isso aconselho descobrir o usuário que vai ser utilizado e adicionar as pastas no mesmo grupo deste usuário, além de setar as permissões via chmod.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Obs.: Caso o usuário que criou as pastas seja o mesmo que vai executar o container docker, **provável que esse passo não seja necessário embora seja recomendado**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por facilidade vou pressupor que o usuário que está sendo utilizado na shell é o mesmo que vai subir o docker, então vamos lá!&lt;/p&gt;

&lt;p&gt;1 - Utilizar o comando &lt;code&gt;whoami&lt;/code&gt; para descobrir o usuário atual&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ whoami
alexandre
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 - No meu caso o usuário se chama &lt;em&gt;alexandre&lt;/em&gt; agora vamos checar qual o id do usuário&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ id
uid=1000(alexandre) gid=1000(alexandre) grupos=1000(alexandre),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),116(lpadmin),987(docker)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3 - Está vendo o valor de &lt;code&gt;uid&lt;/code&gt; que precede o nome do nosso usuário em parenteses? O valor dentro dele que queremos, no meu caso é &lt;em&gt;1000&lt;/em&gt;, vamos precisar disso quando formos criar os usuários com acesso ao SFTP&lt;/p&gt;

&lt;p&gt;4 - Adicione as pastas necessárias no grupo, com o comando &lt;em&gt;chown&lt;/em&gt; na pasta que estamos trabalhando, com o usuário e grupo no seguinte formato &lt;em&gt;usuario:grupo&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chown -R alexandre:alexandre ./sftp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5 - Adicionando as permissões, para quem &lt;strong&gt;criou&lt;/strong&gt; o arquivo e quem está no &lt;strong&gt;grupo&lt;/strong&gt; poder tanto &lt;em&gt;ler&lt;/em&gt;, &lt;em&gt;editar&lt;/em&gt; e &lt;em&gt;excluir&lt;/em&gt; os dados e para o resto poder só executar a leitura (774)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod 774 -R ./sftp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Criar o arquivo de usuários
&lt;/h2&gt;

&lt;p&gt;Essa aplicação que vamos utilizar hoje ela permite que os usuários sejam passados via arquivo, eu acho essa forma mais fácil de gerenciar que via comando ou env então bora criar esse arquivo.&lt;/p&gt;

&lt;p&gt;Basta criar um arquivo &lt;em&gt;users.conf&lt;/em&gt; e adicionar cada informação do usuário no seguinte formato &lt;code&gt;usuario:senha:uid&lt;/code&gt; vou explicar cada campo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;usuario:&lt;/strong&gt; Pode ser qualquer nome de usuario, inclusive o do seu usuário atual (meu caso)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;senha:&lt;/strong&gt; Vou deixar vazio esse campo pois vamos utilizar uma chave &lt;em&gt;SSH&lt;/em&gt; para autenticação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;uid:&lt;/strong&gt; O uid que pegamos no ponto passado &lt;em&gt;(caso seja um apressadinho tente 1000 ou 1001)&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O conteúdo do meu &lt;strong&gt;users.conf&lt;/strong&gt; ficará desse jeito:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alexandre::1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gerando as chaves SSH
&lt;/h2&gt;

&lt;p&gt;Para gerar as chaves eu vou utilizar o &lt;em&gt;ssh-keygen&lt;/em&gt; que já vem instalado no ubuntu normalmente.&lt;/p&gt;

&lt;p&gt;1 - Entre na pasta &lt;em&gt;creds&lt;/em&gt; dentro de &lt;em&gt;/sftp&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;2 - Utilize o comando &lt;strong&gt;ssh-keygen -t rsa&lt;/strong&gt; e digite o nome do arquivo, pode ser qualquer um eu coloquei &lt;em&gt;key&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;3 - Após isso ele pergunta se você quer adicionar uma senha, é opcional, eu não costumo adicionar, lembrando que se adicionada ela será pedida em cada acesso&lt;/p&gt;

&lt;h2&gt;
  
  
  Após a Odisséia Ulisses retorna à casa
&lt;/h2&gt;

&lt;p&gt;Agora é a melhor hora, segue o docker compose da felicidade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  sftp:
    image: atmoz/sftp
    restart: 'unless-stopped' 
    volumes:
      - ./files:/home/alexandre/upload                          # Os arquivos serão mantidos aqui
      - ./creds/key.pub:/home/alexandre/.ssh/keys/key.pub:ro    # Compartilhando as chaves com o serviço
      - ./users.conf:/etc/sftp/users.conf:ro                    # Adicionando os usuários
    ports:
      - 2222:22                                                 # A porta exposta será a 2222
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E para subir tudo basta executar:&lt;/p&gt;

&lt;h2&gt;
  
  
  Como conectar
&lt;/h2&gt;

&lt;p&gt;Caso queira uma dica de um client para conectar ao seu recém criado serviço eu recomendo o clássico Filezilla, segue abaixo como o utilizar:&lt;/p&gt;

&lt;p&gt;1 - Abra o &lt;em&gt;gerenciador de sites&lt;/em&gt; na aba _Arquivo&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%2F4aogwbbd7x6lbtgxccuu.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%2F4aogwbbd7x6lbtgxccuu.png" alt="Arquivo/gerenciador de sites" width="800" height="645"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2 - Segue as configurações sugeridas:&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%2Fbfnfamlvizl606713qdh.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%2Fbfnfamlvizl606713qdh.png" alt="gerenciador de sites/configuracao" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocolo:&lt;/strong&gt; SFTP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host:&lt;/strong&gt; localhost&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Porta:&lt;/strong&gt; 2222 - Normalmente é a 22 mas redireciono para a 2222, &lt;em&gt;faço isso para evitar conflitos&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tipo de Logon:&lt;/strong&gt; Arquivo chave&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usuário:&lt;/strong&gt; alexandre - Adicione o usuário do arquivo &lt;em&gt;users.conf&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arquivo com chave:&lt;/strong&gt; Aponte para a chave privada (sem o .pub) que criou no passo [Gerando a chave SSH]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cor de fundo:&lt;/strong&gt; ai vai do seu gosto&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3 - Voilá, agora é só aproveitar para realizar seus deploys duvidosos ou guardar seus arquivos da steam verde no seu SFTP favorito (amor de pai e mãe não tem medida)&lt;/p&gt;

&lt;h2&gt;
  
  
  Isso é tudo, pessoal!
&lt;/h2&gt;

&lt;p&gt;Espero que tenha sido útil para vocês, ele ficou um pouco mais extenso que do que eu desejava porém quis deixar tudo bem explicado (eu espero que esteja mesmo 😅).&lt;/p&gt;

&lt;p&gt;Porém caso tenha dúvidas só me chamar na caixa de comentários que vou estar feliz em responder qualquer pergunta!.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>security</category>
      <category>beginners</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Dockerfile - Node with TypeScript</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Sat, 20 Dec 2025 21:38:01 +0000</pubDate>
      <link>https://dev.to/xandecodes/dockerfile-node-with-typescript-51g</link>
      <guid>https://dev.to/xandecodes/dockerfile-node-with-typescript-51g</guid>
      <description>&lt;p&gt;Good morning! Hope you’re all doing well. My fellow tech enthusiasts from the land of AI hype &lt;em&gt;still waiting for them to take our jobs so we can finally retire, right?&lt;/em&gt; 🙏&lt;/p&gt;

&lt;p&gt;But while that doesn't happen, I’d like to share a Dockerfile structure I’ve been working on. Modesty aside, I think the result is pretty solid 😎. In my case, the initial image was around 1GB, and after some tweaks, it dropped to about 161MB (according to &lt;a href="https://github.com/wagoodman/dive" rel="noopener noreferrer"&gt;Dive&lt;/a&gt;)); it’s worth noting this was for a simple app built with [Hono](&lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;https://hono.dev/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**Note:** Keep in mind that your application might not end up the exact same size. It could be larger or smaller depending on your codebase and the dependencies installed in your project.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:lts-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node package*.json tsconfig.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node /src ./src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--silent&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev 

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:lts-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node", "./dist/index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  For those not in a rush
&lt;/h2&gt;

&lt;p&gt;While this Dockerfile covers most use cases, I’ll share some tips on how to use it and explain what happens in each step so you can customize it for your own needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposing the application
&lt;/h3&gt;

&lt;p&gt;First and most importantly: for your application to be accessible, it must be listening on the correct network interface (remember 0.0.0.0?). You also need to publish the container ports to the host machine. Here is an example command for an app using port 3000, which is the same one defined in the Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run image_name:image_tag &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using Docker Compose, the ports field handles this for you. A basic configuration would look like this:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node_api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breaking it down
&lt;/h3&gt;

&lt;p&gt;Now, let's look at the specifics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;1. Multi-stage Build:&lt;/strong&gt; The first block (the build phase) handles downloading dependencies and compiling the application. Once the code is transpiled, dev dependencies are removed to shrink the final image size.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2. Base Image:&lt;/strong&gt; I’m using the latest lts-alpine image (it makes keeping this post updated easier 😅). However, a best practice would be to specify both the Node and Alpine versions or use an image hash; this makes your build even more predictable and less prone to breaking changes.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt; FROM node:24.12.0-alpine3.23 as BUILDER
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s"&gt;node@sha256:c921b97d4b74f51744057454b306b418cf693865e73b8100559189605f6955b8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;3. Security &amp;amp; Execution:&lt;/strong&gt; In the second stage, I only copy what is strictly necessary for the runtime. I then set the user to node (which comes by default in official images). This is crucial because, if omitted, Docker defaults to the root user, which can create security vulnerabilities.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;4. Entrypoint:&lt;/strong&gt; Finally, I expose port 3000 (adjust to whichever port your app uses) and use the ENTRYPOINT command. Here, I run the transpiled JavaScript directly. Note that you could also use a script defined in your package.json if you prefer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Goodbye! 👏
&lt;/h3&gt;

&lt;p&gt;That’s it! Thanks for reading. I hope this article was helpful. I’ve looked for many Docker images in the past, but they always seemed too complex or lacked something I needed. While this model might not solve every single case, it serves as a great foundation for using Docker with Node and TypeScript.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>node</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Dockerfile - Node com Typescript</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Sat, 20 Dec 2025 18:23:53 +0000</pubDate>
      <link>https://dev.to/xandecodes/dockerfile-node-com-typescript-1ga4</link>
      <guid>https://dev.to/xandecodes/dockerfile-node-com-typescript-1ga4</guid>
      <description>&lt;p&gt;Bom dia, tudo bem? Espero que sim! Meus caros conterrâneos da terra do hype em Inteligência Artificial &lt;em&gt;sigo no aguardo esperançoso de que elas tirem nossos trabalhos&lt;/em&gt; 🙏.&lt;/p&gt;

&lt;p&gt;Mas, enquanto isso não acontece, gostaria de compartilhar uma estrutura de Dockerfile que criei durante alguns estudos. E não querendo faltar com humildade, acredito que o resultado ficou muito bom 😎.&lt;/p&gt;

&lt;p&gt;No meu caso, a imagem inicial tinha cerca de 1GB e após alguns ajustes ficou com cerca de &lt;strong&gt;161MB&lt;/strong&gt; (segundo o &lt;a href="https://github.com/wagoodman/dive" rel="noopener noreferrer"&gt;Dive&lt;/a&gt;), vale ressaltar que é uma app &lt;br&gt;
simples feita em &lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Hono&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**Observação:** Lembrando que isso não quer dizer que sua aplicação terá esse mesmo tamanho, pode ficar maior ou menor, isso vai depender do tamanho da base de código e das dependências instaladas no projeto.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:lts-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node package*.json tsconfig.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node /src ./src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--silent&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev 

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:lts-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node", "./dist/index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Para quem não está com pressa
&lt;/h2&gt;

&lt;p&gt;Embora esse Dockerfile contemple muitos casos, vou passar algumas dicas de como usar e explicar um pouco melhor o que fiz em cada passo para que você possa customizar e poder rodar sua aplicação da melhor forma possível!&lt;/p&gt;

&lt;h3&gt;
  
  
  Expor a aplicação
&lt;/h3&gt;

&lt;p&gt;Primeiro e mais importante para sua aplicação ser exposta, além dela estar aceitando requisições externas (&lt;a href="https://dev.to/engrmark/what-is-host0000-goa"&gt;lembra do &lt;code&gt;0.0.0.0&lt;/code&gt;?&lt;/a&gt;), é preciso também publicar as portas do container para a máquina que está executando a imagem.&lt;/p&gt;

&lt;p&gt;Segue comando de exemplo para uma aplicação que utiliza a porta &lt;strong&gt;3000&lt;/strong&gt;, que é a mesma definida no Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run nome_da_imagem:tag_da_imagem &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caso esteja utilizando &lt;em&gt;Docker Compose&lt;/em&gt;, o campo &lt;code&gt;ports&lt;/code&gt; já se encarrega disso. Um manifesto básico para essa aplicação seria algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  node_api:
    build: .
    ports:
      - 3000:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trocando em miúdos
&lt;/h3&gt;

&lt;p&gt;Agora gostaria de explicar de forma mais detalhada o que estou fazendo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;1. Build Multistage:&lt;/strong&gt; O primeiro bloco que seria a fase de build se encarrega de além de baixar as dependências realizar o build da aplicação. Inclusive após gerar o código transpilado, as dependências de desenvolvimento são removidas para diminuir o tamanho final da imagem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2. Imagem Base:&lt;/strong&gt; Aqui vale um ponto de atenção pois estou utilizando a última imagem do Node e do Alpine disponibilizada (fica mais fácil manter o texto atualizado dessa forma😅). &lt;/p&gt;

&lt;p&gt;Porém uma melhor prática seria especificar tanto a versão do Node quanto a do Alpine ou utilizar a hash da imagem dessa forma teríamos uma imagem ainda menos passível de alterações no comportamento.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:24.12.0-alpine3.23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;BUILDER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;ou&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node@sha256:c921b97d4b74f51744057454b306b418cf693865e73b8100559189605f6955b8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;3. Segurança e Execução:&lt;/strong&gt; Na segunda parte, só realizei a cópia do que é estritamente necessário para a execução da aplicação. Após isso, determino que o usuário &lt;code&gt;node&lt;/code&gt; (que já vem por padrão nas imagens oficiais) seja utilizado. Isso é importante, pois, caso não seja feito, o usuário &lt;code&gt;root&lt;/code&gt; será utilizado e isso pode deixar brechas de segurança na imagem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;4. Entrypoint:&lt;/strong&gt; E então finalmente exponho a porta 3000 que é a utilizada pela minha aplicação &lt;strong&gt;não deixe de colocar a que sua aplicação está utilizando&lt;/strong&gt; e utilizo o comando &lt;em&gt;ENTRYPOINT&lt;/em&gt;.&lt;br&gt;
Aqui executo o JavaScript transpilado na camada anterior de forma direta, mas um comando explicitado no &lt;em&gt;package.json&lt;/em&gt; também poderia estar sendo utilizado.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tchau 👏
&lt;/h3&gt;

&lt;p&gt;Então é isso, obrigado pela leitura! Espero que esse artigo tenha sido de alguma ajuda. &lt;/p&gt;

&lt;p&gt;Eu já pesquisei algumas imagens mas sempre pareciam um pouco complexas demais ou faltava algo que eu precisava. Dessa forma, acho que este modelo não resolve todos os casos, mas serve como uma boa base para utilizar Docker em conjunto com Node e Typescript.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>node</category>
      <category>typescript</category>
    </item>
    <item>
      <title>🧩 Automated Documentation using MkDocs and Python</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Mon, 27 Oct 2025 21:39:11 +0000</pubDate>
      <link>https://dev.to/xandecodes/automated-documentation-using-mkdocs-and-python-27</link>
      <guid>https://dev.to/xandecodes/automated-documentation-using-mkdocs-and-python-27</guid>
      <description>&lt;p&gt;Something I really enjoy is browsing through library documentation — honestly, good documentation is something I find beautiful to see and also difficult to maintain 😅.&lt;/p&gt;

&lt;p&gt;Now imagine: every time a change is made to a behavior, having to go to the page that explains it, copy and paste the code, write what changed, run the build, check if everything is correct... wait, that's not how it should be! — I'm already tired just thinking about it 😤.&lt;/p&gt;

&lt;h2&gt;
  
  
  🐛 Tool to be documented
&lt;/h2&gt;

&lt;p&gt;I created a very simple tool that makes a call to the PokeAPI, fetches information about a Pokémon, and displays the result in the terminal.&lt;/p&gt;

&lt;p&gt;The source code can be found here: &lt;a href="https://github.com/XandeCoding/codigos-de-artigos/tree/main/python/documentacao_automatica" rel="noopener noreferrer"&gt;Automated Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Talking a bit more about the project: I used Poetry to manage the virtual environment and keep the libraries isolated from the rest of the system (nobody wants to clutter their PC with libs, right?).&lt;br&gt;
But you can use any other tool, like pyenv or virtualenvwrapper — it doesn't matter.&lt;/p&gt;

&lt;p&gt;The installed libraries are listed in pyproject.toml, so just install them and start playing.&lt;/p&gt;
&lt;h2&gt;
  
  
  📝 How to document
&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%2F54xr5ms28n2irn7x3eia.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%2F54xr5ms28n2irn7x3eia.png" alt="Pokémon API access code PokeAPI" width="800" height="711"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a small piece of the code I'll use as an example.&lt;br&gt;
You must have noticed those """ — they are Docstrings, used to document code. It's a very common convention for documenting modules, classes, functions... well, anything.&lt;br&gt;
If you're going to document something, I highly recommend using docstrings 😜.&lt;/p&gt;

&lt;p&gt;These docstrings are what make the magic happen: we'll make them be automatically read and inserted into the corresponding documentation pages.&lt;br&gt;
With the docstrings ready and explaining what each function does, we can proceed.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⬆️ Installing MkDocs
&lt;/h2&gt;

&lt;p&gt;To install using Poetry it's very simple — just use poetry add, or pip install if you're not using it.&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%2Fb6lb7pla7ap4x8f01aiu.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%2Fb6lb7pla7ap4x8f01aiu.png" alt="Code showing how to install mkdocs" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; When installing mkdocstrings, replace [python] with the language you're using.&lt;br&gt;
Check the official documentation to verify support: Mkdocstrings Docs.&lt;/p&gt;

&lt;p&gt;We installed MkDocs, a nice theme called mkdocs-material (a matter of taste, but I really like it 😄), and finally mkdocstrings, a plugin that scans the project and inserts the docstrings into the corresponding pages.&lt;/p&gt;

&lt;p&gt;So now? What do we do with all this? 🤔 Don't worry, you don't need to send a letter to a TV show — it's much simpler than that 😂.&lt;br&gt;
Following the step-by-step below, your documentation will be ready in no time:&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1️⃣
&lt;/h3&gt;

&lt;p&gt;Open a terminal inside the project folder and use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkdocs new &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the configuration files and the docs folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2️⃣
&lt;/h3&gt;

&lt;p&gt;Now you should have a file called mkdocs.yml in the project root.&lt;br&gt;
In it, add the following configurations:&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;site_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Automated Documentation&lt;/span&gt; &lt;span class="c1"&gt;# You can use your application's name&lt;/span&gt;

    &lt;span class="na"&gt;theme&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;material&lt;/span&gt; &lt;span class="c1"&gt;# Adds the nice theme&lt;/span&gt;

        &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;search&lt;/span&gt; &lt;span class="c1"&gt;# Plugin that enables search in the documentation&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mkdocstrings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# This one makes the magic happen!&lt;/span&gt;
            &lt;span class="na"&gt;default_handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; Yeah, the code looks a bit ugly, but this way you can just copy and paste — my laziness salutes yours 🙏&lt;/p&gt;

&lt;p&gt;The mkdocstrings plugin is responsible for reading the docstrings and automatically adding them to the corresponding pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3️⃣
&lt;/h3&gt;

&lt;p&gt;Run the commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkdocs build
mkdocs serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, access the documentation through your browser at:&lt;br&gt;
👉 &lt;a href="http://127.0.0.1:8000/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see something similar to the image below:&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%2F2fa6vl45xhc8h9rkxmmm.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%2F2fa6vl45xhc8h9rkxmmm.png" alt="Documentation page index" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4️⃣
&lt;/h3&gt;

&lt;p&gt;When you access it, you'll see an introduction explaining how to add pages — it's very simple!&lt;br&gt;
Just create a .md file inside the docs folder in the project root, and it will be automatically added to the documentation structure.&lt;/p&gt;

&lt;p&gt;Your structure should look similar to this:&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%2Ft2s6pnr4eigb1gbevq5c.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%2Ft2s6pnr4eigb1gbevq5c.png" alt="Folder structure" width="501" height="1050"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5️⃣
&lt;/h3&gt;

&lt;p&gt;Now let's go to the pokemon page, used to document the pokemon module we saw at the beginning.&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%2Fhqa6ko5t0c5laltywr7f.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%2Fhqa6ko5t0c5laltywr7f.png" alt="Documentation markdown file" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the page content, we have a brief introduction and then something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;::: src.pokemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what is this?&lt;br&gt;
This command maps the module to be automatically documented — the path is the same one used in the import.&lt;br&gt;
Don't believe it works? Take a look at the result:&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%2Flh4ypym2c79vd456cu7d.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%2Flh4ypym2c79vd456cu7d.png" alt="Pokémon module documentation" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6️⃣
&lt;/h3&gt;

&lt;p&gt;Enjoy! 😁&lt;/p&gt;

&lt;h2&gt;
  
  
  🎉 The End
&lt;/h2&gt;

&lt;p&gt;In just a few steps, we already have pretty nice documentation!&lt;br&gt;
If you add new modules or want to create more pages, just include the file inside the docs folder, and you're done.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed it!&lt;br&gt;
And if you have any questions (or found any errors — it happens to the best of us 😅), just leave a comment below!&lt;/p&gt;

</description>
      <category>python</category>
      <category>markdown</category>
      <category>documentation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building an Organized API in Golang Using Fiber</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Mon, 27 Oct 2025 21:33:03 +0000</pubDate>
      <link>https://dev.to/xandecodes/building-an-organized-api-in-golang-using-fiber-2pb8</link>
      <guid>https://dev.to/xandecodes/building-an-organized-api-in-golang-using-fiber-2pb8</guid>
      <description>&lt;p&gt;After spending some time exploring Go and struggling a bit with its package system (here I must admit it's actually a great system when used properly), I found myself thinking a lot about the best way to organize the study API I was building.&lt;/p&gt;

&lt;p&gt;I gathered many examples, including from the Fiber repository, and arrived at a structure that I find quite readable and easy to extend with new features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; I'm not saying this is the best way to organize a Go API, but it worked for my needs and I believe it can be useful in many cases where a generic API is required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Does This API Do?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/XandeCoding/codigos-de-artigos/tree/main/golang/api_simples" rel="noopener noreferrer"&gt;&lt;strong&gt;Repository Link&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This API was created to store information about books and has only three endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GET:&lt;/strong&gt; Returns information about a book&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PUT:&lt;/strong&gt; Adds or updates information about a book&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DELETE:&lt;/strong&gt; Deletes book information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I implemented just the basics, using the Fiber framework which has an approach similar to Express.js that I really liked, combined with Go's advantages like lower memory allocation and incredible speed. The data is stored in Redis, which can be initialized using a docker-compose file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;| api_simples
├── docker-compose.yml
├── docs
│   └── estrutura.png
├── go.mod
├── go.sum
├── main.go
├── pkg
│   ├── configurations
│   │   └── database.go
│   ├── entities
│   │   └── book.go
│   ├── handlers
│   │   └── book_handler.go
│   ├── repositories
│   │   ├── book_repository.go
│   │   └── commons.go
│   └── routes
│       ├── book_router.go
│       └── routes.go
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I believe it's more valuable to explain the reasoning behind this organization rather than just listing what each folder contains. I won't follow the exact order above since I think it's clearer to explain in a different sequence:&lt;/p&gt;

&lt;h3&gt;
  
  
  go.mod
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;XandeCoding&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;codigos&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;artigos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;golang&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;api_simples&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="m"&gt;1.19&lt;/span&gt;

&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v9&lt;/span&gt; &lt;span class="n"&gt;v9&lt;/span&gt;&lt;span class="m"&gt;.0.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;beta&lt;/span&gt;&lt;span class="m"&gt;.2&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gofiber&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="m"&gt;.36.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file helps resolve various Go workspace issues, allowing me to create a Go repository anywhere without problems accessing external or local packages.&lt;/p&gt;

&lt;p&gt;To create it, I ran the go mod init command with the GitHub project path as argument (github.com/XandeCoding/codigos-de-artigos/golang/api_simples). While it's not strictly necessary to use the full GitHub path (the project name api_simples would work), I chose to include it since this is a public project. This makes it easier to reference specific files from the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  pkg and main.go
&lt;/h3&gt;

&lt;p&gt;The pkg folder contains the main API code where all features are implemented. The main.go file is only used to initialize the application and contains no implementation logic, serving solely to start the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;main.go&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/XandeCoding/codigos-de-artigos/golang/api_simples/pkg/routes"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gofiber/fiber/v2"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":3000"&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;h3&gt;
  
  
  pkg/configurations
&lt;/h3&gt;

&lt;p&gt;This contains configuration files. In this case, &lt;code&gt;database.go&lt;/code&gt; configures database access. If we had other application or tool configurations used by one or more parts of the application, they would also go here - such as custom Fiber configurations or environment variables.&lt;/p&gt;

&lt;p&gt;Example Redis connection configuration in &lt;strong&gt;database.go&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;configurations&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/go-redis/redis/v9"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;redisDatabase&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"localhost:6379"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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="n"&gt;redisDatabase&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pkg/entities
&lt;/h3&gt;

&lt;p&gt;Entities can be used in various places, particularly in this case where I use them both for receiving data in endpoints and for database operations. Placing them in a common location is quite useful, though in feature-separated package structures, this approach might not be as ideal.&lt;br&gt;
pkg/repositories&lt;/p&gt;

&lt;p&gt;This package contains functions that work directly with the Redis database. They receive the book entity and have functions that insert, update, and delete it from the database. If there were another entity to handle, such as library, it would be in a separate file containing only functions related to that data.&lt;/p&gt;

&lt;p&gt;Fragment from &lt;strong&gt;book_repository.go&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rdb&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetBook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pkg/routes
&lt;/h3&gt;

&lt;p&gt;Following the example of other parts of the application, I separated routes into different files. Even though we have a routes.go file that initializes these routes, it's helpful to keep routes for specific resources separate for better readability and understanding for others who might maintain the code.&lt;/p&gt;

&lt;p&gt;Route initialization in &lt;strong&gt;routes.go&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;AddRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bookRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In book_router.go, I only specify the routes, methods, and handler functions located in another part of the application. Another important aspect is that this structure allows us to create instances that can be reused across all endpoints for this specific resource - in this case, a Redis database connection instance.&lt;/p&gt;

&lt;p&gt;Fragment from &lt;strong&gt;book_router.go&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;bookRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bookRepository&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;repositories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRepository&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/book/:name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBookHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookRepository&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetBookHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookRepository&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/book/:name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteBookHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookRepository&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pkg/handlers
&lt;/h3&gt;

&lt;p&gt;In handlers, I place the functions called by the endpoints. For example, the PUT /book endpoint calls the SetBookHandler function in book_handler.go, which returns the function to be executed when this resource is accessed.&lt;/p&gt;

&lt;p&gt;Code for the &lt;strong&gt;SetBookHandler&lt;/strong&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SetBookHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookRepository&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;repositories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BodyParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Invalid entry data"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;book_name_normalized&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"book:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;book_name_normalized&lt;/span&gt;
        &lt;span class="n"&gt;bookRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetBook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&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;
  
  
  That's It!
&lt;/h2&gt;

&lt;p&gt;I hope this helps with some of the initial challenges when building an API, especially in a language we're not very familiar with. This initial structure worked well for me, but any comments or feedback are always welcome for continuous improvement. Until next time! 👋&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Docker Compose - Simple Kafka</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Mon, 27 Oct 2025 21:30:16 +0000</pubDate>
      <link>https://dev.to/xandecodes/docker-compose-simple-kafka-1jp4</link>
      <guid>https://dev.to/xandecodes/docker-compose-simple-kafka-1jp4</guid>
      <description>&lt;p&gt;Hey folks! How’s it going? 😄&lt;br&gt;&lt;br&gt;
I’m here to share a simple script I learned while working on a college project using &lt;strong&gt;Kafka&lt;/strong&gt; and &lt;strong&gt;Python&lt;/strong&gt;. Maybe later I’ll make another post showing how I connected everything and created the &lt;em&gt;consumers&lt;/em&gt; and &lt;em&gt;producers&lt;/em&gt; in Python.&lt;/p&gt;

&lt;p&gt;The goal is to keep the setup as simple as possible, since most of the examples I found were a bit complex and included tools I didn’t actually need.&lt;/p&gt;

&lt;p&gt;In this final version, I only kept &lt;strong&gt;Zookeeper&lt;/strong&gt; (used by Kafka to coordinate services), &lt;strong&gt;Kafka&lt;/strong&gt; itself, and a &lt;strong&gt;dashboard&lt;/strong&gt; to easily visualize what’s going on — which helps &lt;em&gt;a lot&lt;/em&gt;!&lt;br&gt;&lt;br&gt;
You can access the dashboard through your browser at &lt;code&gt;localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🐳 Docker Compose
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;zookeeper&lt;/span&gt;&lt;span class="pi"&gt;:&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;confluentinc/cp-zookeeper:latest&lt;/span&gt;
          &lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;ZOOKEEPER_CLIENT_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2181&lt;/span&gt;
                      &lt;span class="na"&gt;ZOOKEEPER_TICK_TIME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2000&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="s"&gt;2181:2181&lt;/span&gt;

                                    &lt;span class="s"&gt;kafka&lt;/span&gt;&lt;span class="err"&gt;:&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;confluentinc/cp-kafka:latest&lt;/span&gt;
                                            &lt;span class="s"&gt;depends_on&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                                                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;zookeeper&lt;/span&gt;
                                                      &lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                                                            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;29092:29092&lt;/span&gt;
                                                                &lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                                                                      &lt;span class="na"&gt;KAFKA_BROKER_ID&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;KAFKA_ZOOKEEPER_CONNECT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zookeeper:2181&lt;/span&gt;
                                                                                  &lt;span class="s"&gt;KAFKA_ADVERTISED_LISTENERS&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092&lt;/span&gt;
                                                                                        &lt;span class="s"&gt;KAFKA_LISTENER_SECURITY_PROTOCOL_MAP&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT&lt;/span&gt;
                                                                                              &lt;span class="s"&gt;KAFKA_INTER_BROKER_LISTENER_NAME&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PLAINTEXT&lt;/span&gt;
                                                                                                    &lt;span class="s"&gt;KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

                                                                                                      &lt;span class="na"&gt;kafka-ui&lt;/span&gt;&lt;span class="pi"&gt;:&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;provectuslabs/kafka-ui&lt;/span&gt;
                                                                                                              &lt;span class="s"&gt;container_name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kafka-ui&lt;/span&gt;
                                                                                                                  &lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                                                                                                                        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
                                                                                                                            &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
                                                                                                                                &lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                                                                                                                                      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KAFKA_CLUSTERS_0_NAME=local&lt;/span&gt;
                                                                                                                                            &lt;span class="s"&gt;- KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092&lt;/span&gt;
                                                                                                                                                  &lt;span class="s"&gt;- KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🧭 A Few Notes ❗
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Kafka&lt;/strong&gt; service is exposed on port &lt;strong&gt;29092&lt;/strong&gt;, so when connecting, use &lt;code&gt;localhost:29092&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;If you make any changes, check that all services are still connected properly.
For example: if you change Kafka’s port, you’ll also need to update it in both &lt;strong&gt;kafka-ui&lt;/strong&gt; and &lt;strong&gt;zookeeper&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that’s all for today, folks! See you next time! 👋&lt;/p&gt;

</description>
      <category>docker</category>
      <category>tutorial</category>
      <category>kafka</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🥖 Bun e Node – Será que o pãozinho tem chance?</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Thu, 07 Aug 2025 22:13:15 +0000</pubDate>
      <link>https://dev.to/xandecodes/bun-e-node-sera-que-o-paozinho-tem-chance-123g</link>
      <guid>https://dev.to/xandecodes/bun-e-node-sera-que-o-paozinho-tem-chance-123g</guid>
      <description>&lt;p&gt;Bem-vindos, meus companheiros marginalizados, insultados e que passam por vexames apenas por usar JavaScript no backend (e jogar no bicho às sextas — com fé vem coelho dessa vez 🙏).&lt;/p&gt;

&lt;p&gt;Como muitos têm acompanhado, nosso ecossistema vem crescendo bastante, não só com novas bibliotecas e funcionalidades, mas também com novas &lt;em&gt;runtimes&lt;/em&gt; para executar código JavaScript no backend — seja para realizar consultas em bancos, fornecer dados via API REST, entregar arquivos estáticos ou outras aplicações.&lt;/p&gt;

&lt;p&gt;Mas queria me aprofundar mais no contexto de runtimes neste momento. Assim como os dinossauros do &lt;em&gt;Jurassic Park&lt;/em&gt; 🦖 evoluíram, começaram a se reproduzir e formar belas famílias devoradoras de pessoas, as runtimes também evoluíram. Hoje temos três pesos pesados no ringue nosso amado &lt;strong&gt;Node.js&lt;/strong&gt;, o &lt;strong&gt;Deno&lt;/strong&gt; (que inclusive é do mesmo criador — o que o ressentimento pela ex não faz, hein? 😅), e o &lt;strong&gt;Bun&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Como já brinquei um pouco com o famoso Deno &lt;a href="https://dev.to/xandecodes/criando-uma-api-de-paginas-estaticas-basica-com-deno-t-rex-5h8o"&gt;neste artigo sobre páginas estáticas&lt;/a&gt;, agora chegou a vez de testar o Bun.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏪ Um breve resumo dos últimos episódios
&lt;/h2&gt;

&lt;p&gt;O Node.js foi lançado em 2009 e possibilitou utilizar JavaScript no backend. Desde então, tem crescido em popularidade. Acredito que isso se deve ao fato de ser uma linguagem de fácil adaptação, até mesmo para quem não tem muita intimidade com programação. Também pesa a sua forte presença no frontend e a acessibilidade direta pelo navegador — para muitos, foi a primeira linguagem com a qual tiveram contato.&lt;/p&gt;

&lt;p&gt;E tudo isso com uma performance admirável, graças à V8, a &lt;em&gt;engine&lt;/em&gt; do Chrome ⚙️. Que sempre foi um navegador perfomático, e usar a mesma engine para rodar código no servidor foi um golaço. Estamos há mais de uma década criando bugs e &lt;em&gt;queries&lt;/em&gt; mal otimizadas em JavaScript. 🫠&lt;/p&gt;

&lt;p&gt;Mas nada é perfeito. Mesmo sendo mantido por engenheiros e engenheiras muito talentosos, o Node.js começou a enfrentar alternativas — e uma delas é o &lt;strong&gt;Bun&lt;/strong&gt;, que é o foco deste texto.&lt;/p&gt;

&lt;h2&gt;
  
  
  🍔 O que é o Bun? (É de comer? Às vezes, sim)
&lt;/h2&gt;

&lt;p&gt;Para começar: o Bun é uma runtime feita do zero com uma linguagem recente chamada &lt;a href="https://www.youtube.com/watch?v=kxT8-C1vmd4" rel="noopener noreferrer"&gt;Zig&lt;/a&gt;, pensada com foco em segurança e performance.&lt;/p&gt;

&lt;p&gt;O objetivo do Bun é entregar &lt;strong&gt;performance&lt;/strong&gt; e uma &lt;strong&gt;API padrão&lt;/strong&gt; que ajude na produtividade. O &lt;em&gt;package manager&lt;/em&gt; que o acompanha funciona muito bem, inclusive.&lt;/p&gt;

&lt;p&gt;Diferente do Node.js e do Deno, o Bun utiliza a engine do Safari para rodar o JavaScript — famosa por sua velocidade, mas também conhecida por ser mais difícil de se desenvolver para ela. Um ponto muito positivo é o suporte &lt;strong&gt;nativo ao TypeScript&lt;/strong&gt; (sim, eu sou um junkie por tipos, me julgue 😅 — mas se eu for concatenar uma &lt;em&gt;string&lt;/em&gt; com um objeto, quero saber conscientemente que estou fazendo uma cachorrada).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;📌 Obs.:&lt;/strong&gt; O Node.js tem melhorado muito o suporte a TypeScript, mas algumas funcionalidades ainda estão em fase experimental 🧪.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧪 O que dá pra fazer com o Bun?
&lt;/h2&gt;

&lt;p&gt;Agora que já contei muita lorota 😄, queria mostrar um humilde benchmark que fiz para testar o brinquedo. Nele, foi feita a leitura de um arquivo JSON, que depois era &lt;em&gt;parseado&lt;/em&gt; e respondido numa requisição. Simples assim:&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%2Fkvy2zo34h56a4bwoacy1.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%2Fkvy2zo34h56a4bwoacy1.png" alt="Benchmark de teste de carga" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;📊 Obs.:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Benchmark realizado em um i7-1255U com 8GB de memória&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low Load&lt;/strong&gt;: 125 conexões e 100.000 requisições
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medium Load&lt;/strong&gt;: 1.000 conexões e 100.000 requisições
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy Load&lt;/strong&gt;: 10.000 conexões e 1.000.000 requisições
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Os testes foram feitos com o &lt;a href="https://github.com/codesenberg/bombardier" rel="noopener noreferrer"&gt;bombardier&lt;/a&gt;. Aqui está o repositório com os testes: &lt;a href="https://github.com/XandeCoding/codigos-de-artigos/tree/main/multi_language/bun%20e%20node" rel="noopener noreferrer"&gt;bun-e-node&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Também recomendo um benchmark mais completo e profissional: &lt;a href="https://www.youtube.com/watch?v=DpDHPoStZZ8" rel="noopener noreferrer"&gt;Deno vs. Node.js vs. Bun: Performance Comparison 2025&lt;/a&gt;&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%2Fnv15eexr9oxf6eiuh4wi.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%2Fnv15eexr9oxf6eiuh4wi.png" alt="segundo benchmark de teste de carga" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Analisando com cuidado, vemos algumas qualidades e defeitos também:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⚡ &lt;strong&gt;Velocidade&lt;/strong&gt; – Realmente entregou uma performance superior. No último teste de carga intensa, teve média de &lt;strong&gt;300.96ms&lt;/strong&gt;, enquanto o Node respondia em &lt;strong&gt;1000.42ms&lt;/strong&gt; — cerca de 3x mais rápido.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧠 &lt;strong&gt;Recursos&lt;/strong&gt; – Aqui surgem controvérsias. No primeiro teste, o Bun consumiu menos memória e CPU. No segundo, porém, teve um pico de &lt;strong&gt;1.51 GiB de memória&lt;/strong&gt;! Isso já foi corrigido na &lt;a href="https://github.com/oven-sh/bun/issues/17063" rel="noopener noreferrer"&gt;issue #17063&lt;/a&gt;, mas é um risco a ser considerado para quem quer ser early adopter 🧪.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💭 Considerações
&lt;/h2&gt;

&lt;p&gt;Embora os benchmarks sejam empolgantes, nem tudo são flores no dia a dia. Nos meus testes, por exemplo, usar o roteador padrão do Bun trouxe um &lt;em&gt;overhead&lt;/em&gt; muito maior do que implementar algo manualmente. A diferença foi tão grande que achei que havia um bug no código.&lt;/p&gt;

&lt;p&gt;Apesar de ter sido feito do zero, com base nos aprendizados das outras runtimes e com forte foco em performance, o Bun &lt;strong&gt;ainda não tem a bagagem&lt;/strong&gt; do Node.js. São mais de 10 anos de história e uma comunidade gigantesca.&lt;/p&gt;

&lt;p&gt;Isso faz muita diferença. Caso surja um problema no Node.js, é bem provável que alguém já tenha passado por isso e compartilhado uma solução. Sem contar que muitas bibliotecas são construídas com o Node como base.&lt;/p&gt;

&lt;p&gt;Portanto, se estiver pensando em fazer uma troca, analise bem: sua aplicação realmente vai ganhar algo com isso? Porque dificilmente ela vai se comportar exatamente igual — para o bem ou para o mal.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Conclusão
&lt;/h2&gt;

&lt;p&gt;É isso! Espero ter dado uma visão panorâmica sobre essas duas runtimes. O ecossistema de Backend com JavaScript está em plena expansão, e isso é ótimo para nós devs. A cada dia surgem ferramentas mais modernas, estáveis e interessantes, disputando espaço com os irmãos mais velhos 🧓.&lt;/p&gt;

</description>
      <category>node</category>
      <category>bunjs</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Spinning Up PostgreSQL with Docker Compose + Dashboard</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Mon, 21 Apr 2025 15:26:09 +0000</pubDate>
      <link>https://dev.to/xandecodes/spinning-up-postgresql-with-docker-compose-dashboard-g1m</link>
      <guid>https://dev.to/xandecodes/spinning-up-postgresql-with-docker-compose-dashboard-g1m</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, database lovers!&lt;/em&gt; (I know that doesn’t really exist, but hey — I'm trying to break the ice here 🫠)&lt;/p&gt;

&lt;p&gt;I want to share a script to spin up &lt;strong&gt;PostgreSQL&lt;/strong&gt; using &lt;code&gt;docker-compose&lt;/code&gt;, which can be useful for anyone&lt;br&gt;
looking for something really simple: just get it running, with a pretty dashboard to visualize data and run some queries too.&lt;/p&gt;

&lt;p&gt;Without further ado, let’s get to the code:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;database_postgres&lt;/span&gt;&lt;span class="pi"&gt;:&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;postgres: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="s"&gt;5432:5432&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database_name&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="s"&gt;${HOME}/postgres-data/:/var/lib/postgresql/data&lt;/span&gt;    

  &lt;span class="na"&gt;pgweb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pgweb&lt;/span&gt;
    &lt;span class="na"&gt;restart&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sosedoff/pgweb&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="s"&gt;8081:8081&lt;/span&gt;
    &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;database_postgres:database_postgres&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGWEB_DATABASE_URL=postgres://username:password@database_postgres:5432/database_name?sslmode=disable&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;database_postgres&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🛠 Explaining in Detail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;database_postgres&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Nothing new under the sun here. Just some variables to initialize the database with a user.&lt;/p&gt;

&lt;p&gt;But the &lt;strong&gt;trick&lt;/strong&gt; is in the &lt;code&gt;volumes&lt;/code&gt;:&lt;br&gt;&lt;br&gt;
Here we make sure the data is saved in a directory inside your &lt;code&gt;HOME&lt;/code&gt;,&lt;br&gt;&lt;br&gt;
so all database data will persist there.&lt;/p&gt;

&lt;p&gt;👉 This way, if you run &lt;code&gt;docker-compose down&lt;/code&gt; (or &lt;code&gt;docker compose down&lt;/code&gt;, depending on your version),&lt;br&gt;&lt;br&gt;
you won’t lose your data.&lt;/p&gt;

&lt;p&gt;It's not ideal for production, but it can help you in a lot of cases! 😂&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;code&gt;pgweb&lt;/code&gt; (Dashboard)
&lt;/h3&gt;

&lt;p&gt;We’ve got two cool things here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;connection URL&lt;/strong&gt; is passed via environment variable, which is super convenient.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;links&lt;/code&gt; creates a connection between the services — it defines an alias, allowing the &lt;code&gt;pgweb&lt;/code&gt; container
to access the &lt;code&gt;database_postgres&lt;/code&gt; by name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, &lt;code&gt;depends_on&lt;/code&gt; ensures that the database starts &lt;strong&gt;before&lt;/strong&gt; the dashboard,&lt;br&gt;&lt;br&gt;
which helps prevent connection issues.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 Running Everything
&lt;/h2&gt;

&lt;p&gt;After running a simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can access your dashboard in the browser at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8081/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There you can run queries or just visualize your data — all right in your browser,&lt;br&gt;&lt;br&gt;
no need to install anything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✌️ That’s it!
&lt;/h2&gt;

&lt;p&gt;I hope you found this article useful.&lt;br&gt;&lt;br&gt;
If you have any questions, feel free to reach out in the comments 🤙&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Docker Compose - FTP Server</title>
      <dc:creator>Alexandre Fernandes dos Santos</dc:creator>
      <pubDate>Wed, 16 Apr 2025 15:03:50 +0000</pubDate>
      <link>https://dev.to/xandecodes/docker-compose-ftp-server-43f9</link>
      <guid>https://dev.to/xandecodes/docker-compose-ftp-server-43f9</guid>
      <description>&lt;p&gt;Hello everyone! I'm here to share another &lt;code&gt;docker-compose&lt;/code&gt; script I put together to save some files at home,&lt;br&gt;
using an old PC I had lying around.&lt;br&gt;&lt;br&gt;
So, if you've got a little unused PC and want to use it for backups or to store files — &lt;br&gt;
without filling up the disk on your main computer or relying on the internet — I think this could be a great option!&lt;/p&gt;

&lt;p&gt;I used &lt;strong&gt;ProFTPD&lt;/strong&gt;, which was the easiest and simplest FTP service I found.&lt;br&gt;&lt;br&gt;
I also tested &lt;strong&gt;vsftpd&lt;/strong&gt;, but I found it a bit more complex to set up.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ftp-container&lt;/span&gt;&lt;span class="pi"&gt;:&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;kibatic/proftpd&lt;/span&gt;
    &lt;span class="na"&gt;network_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host&lt;/span&gt;
    &lt;span class="na"&gt;restart&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FTP_LIST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username:password"&lt;/span&gt;
      &lt;span class="na"&gt;USERADD_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--gid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;33&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--uid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;33"&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./files:/home/data"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I'll explain some of the configurations you might want to tweak for your own setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;network_mode —&lt;/strong&gt; This option makes the container use the same network as the host machine&lt;br&gt;
(the PC running the FTP server).&lt;br&gt;
That way, the service becomes visible on your local network — so you can access it from your phone or any other device connected to the same network.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;FTP_LIST —&lt;/strong&gt; Here you set the username and password in the format username:password.&lt;br&gt;
You can add more users by separating them with ;.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Note: The password cannot contain special characters.
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;USERADD_OPTIONS —&lt;/strong&gt; Sets the group and default user inside the container.&lt;br&gt;
With this setup, it can create and read files normally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;volumes —&lt;/strong&gt; The first path (./files) is the folder on your computer that will be shared with the FTP.&lt;br&gt;
That’s where your files will be saved. I recommend creating an empty folder named files in the same directory as the script.&lt;br&gt;
The second path (/home/data) is the directory inside the container — this name can be anything you like.&lt;br&gt;
The important part is making sure the first path is correct.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it! Hope it helps. If you have any questions, feel free to reach out. See you next time! 👋&lt;/p&gt;

</description>
      <category>ftp</category>
      <category>docker</category>
      <category>linux</category>
      <category>backup</category>
    </item>
  </channel>
</rss>
