<?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: Rafael Dutra</title>
    <description>The latest articles on DEV Community by Rafael Dutra (@raffaeldutra).</description>
    <link>https://dev.to/raffaeldutra</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4044671%2F7b08d7a0-6321-48ee-9a3b-4782fc00eacd.jpg</url>
      <title>DEV Community: Rafael Dutra</title>
      <link>https://dev.to/raffaeldutra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/raffaeldutra"/>
    <language>en</language>
    <item>
      <title>IaC além do Terraform - Ansible para provisionamento e configuração</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Wed, 02 Sep 2026 09:30:44 +0000</pubDate>
      <link>https://dev.to/apsis-cc/iac-alem-do-terraform-ansible-para-provisionamento-e-configuracao-le9</link>
      <guid>https://dev.to/apsis-cc/iac-alem-do-terraform-ansible-para-provisionamento-e-configuracao-le9</guid>
      <description>&lt;h2&gt;
  
  
  1. Provisionar não é configurar
&lt;/h2&gt;

&lt;p&gt;No artigo anterior desta série, vimos o OpenTofu como uma alternativa (ou substituto direto) ao Terraform para a tarefa de &lt;strong&gt;provisionar&lt;/strong&gt; infraestrutura — criar VMs, redes, bancos de dados gerenciados, buckets. Mas provisionar um servidor é só o primeiro passo: depois que a VM existe, alguém precisa instalar pacotes, configurar usuários, aplicar hardening, subir a aplicação e manter tudo isso consistente ao longo do tempo. É nesse espaço que o &lt;strong&gt;Ansible&lt;/strong&gt; entra — e é comum ver os dois trabalhando juntos no mesmo pipeline, não como concorrentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Onde o Terraform para e o Ansible começa
&lt;/h2&gt;

&lt;p&gt;A distinção mais útil na prática é:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terraform (e OpenTofu)&lt;/strong&gt; são ferramentas de &lt;strong&gt;provisionamento&lt;/strong&gt;: elas conversam com APIs de nuvem para criar, atualizar ou destruir recursos. O modelo mental é declarativo e orientado a &lt;strong&gt;estado desejado do recurso&lt;/strong&gt;: "quero uma VM com esse tipo de instância, nessa rede, com esse disco".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ansible&lt;/strong&gt; é uma ferramenta de &lt;strong&gt;gerenciamento de configuração&lt;/strong&gt;: ela conecta em máquinas já existentes (via SSH, sem precisar de agente instalado) e executa tarefas para deixá-las em um estado desejado: "quero o Nginx instalado, essa versão, esse arquivo de configuração, esse serviço rodando".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Não é incomum ver os dois no mesmo pipeline: o Terraform cria a VM e expõe o IP como output; o Ansible usa esse IP para conectar e configurar o que está dentro dela. Um cuida do "hardware" (ainda que virtual), o outro do "software".&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Conceitos fundamentais do Ansible
&lt;/h2&gt;

&lt;p&gt;Antes de ver exemplos reais, vale fixar o vocabulário:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inventory:&lt;/strong&gt; a lista de máquinas que o Ansible gerencia, agrupadas logicamente (por exemplo, &lt;code&gt;webservers&lt;/code&gt;, &lt;code&gt;databases&lt;/code&gt;). Pode ser um arquivo estático (INI ou YAML) ou gerado dinamicamente (ex.: a partir de tags de uma conta AWS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playbook:&lt;/strong&gt; um arquivo YAML que descreve, em ordem, quais tarefas (&lt;code&gt;tasks&lt;/code&gt;) devem ser executadas em quais grupos de máquinas do inventory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module:&lt;/strong&gt; a unidade de trabalho executada por uma tarefa — existem módulos prontos para praticamente tudo (&lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;copy&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;template&lt;/code&gt;, módulos específicos de nuvem, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role:&lt;/strong&gt; uma forma de empacotar playbooks, variáveis, templates e arquivos relacionados a uma responsabilidade específica (ex.: uma role &lt;code&gt;nginx&lt;/code&gt; que sabe instalar e configurar o Nginx), reutilizável entre projetos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotência:&lt;/strong&gt; assim como o Terraform, o Ansible é projetado para que rodar o mesmo playbook várias vezes produza o mesmo resultado final, sem efeitos colaterais — se um pacote já está instalado, o módulo &lt;code&gt;apt&lt;/code&gt; simplesmente não faz nada na segunda execução.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentless:&lt;/strong&gt; ao contrário de ferramentas como Puppet ou Chef, o Ansible não exige instalar um agente permanente na máquina gerenciada — ele conecta via SSH (ou WinRM, no caso do Windows), copia os módulos necessários, executa e limpa depois. Isso simplifica bastante o setup inicial.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Inventory na prática
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# inventory/hosts.ini
&lt;/span&gt;&lt;span class="nn"&gt;[webservers]&lt;/span&gt;
&lt;span class="err"&gt;web1&lt;/span&gt; &lt;span class="py"&gt;ansible_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10.0.1.10&lt;/span&gt;
&lt;span class="err"&gt;web2&lt;/span&gt; &lt;span class="py"&gt;ansible_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10.0.1.11&lt;/span&gt;

&lt;span class="nn"&gt;[databases]&lt;/span&gt;
&lt;span class="err"&gt;db1&lt;/span&gt; &lt;span class="py"&gt;ansible_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10.0.2.10&lt;/span&gt;

&lt;span class="nn"&gt;[webservers:vars]&lt;/span&gt;
&lt;span class="py"&gt;ansible_user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;deploy&lt;/span&gt;
&lt;span class="py"&gt;ansible_ssh_private_key_file&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;~/.ssh/deploy_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Um playbook real: configurando um servidor web após o provisionamento
&lt;/h2&gt;

&lt;p&gt;Este exemplo assume que a VM já existe (criada por Terraform/OpenTofu) e foca só na parte de configuração: instalar Nginx, criar um usuário de deploy, copiar um arquivo de configuração e garantir que o firewall libere a porta certa.&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="c1"&gt;# playbook-webserver.yml&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;Configurar servidor web&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webservers&lt;/span&gt;
  &lt;span class="na"&gt;become&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;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;

  &lt;span class="na"&gt;tasks&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;Atualizar cache de pacotes&lt;/span&gt;
      &lt;span class="na"&gt;apt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;update_cache&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;cache_valid_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&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;Instalar Nginx&lt;/span&gt;
      &lt;span class="na"&gt;apt&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;nginx&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&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;Criar usuário de deploy&lt;/span&gt;
      &lt;span class="na"&gt;user&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;deploy&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
        &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;www-data&lt;/span&gt;
        &lt;span class="na"&gt;append&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;Copiar configuração customizada do Nginx&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;templates/nginx-site.conf.j2&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/sites-available/app&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0644"&lt;/span&gt;
      &lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recarregar Nginx&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;Habilitar o site&lt;/span&gt;
      &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/sites-available/app&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/sites-enabled/app&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;link&lt;/span&gt;
      &lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recarregar Nginx&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;Liberar porta HTTP no firewall (ufw)&lt;/span&gt;
      &lt;span class="na"&gt;ufw&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allow&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80"&lt;/span&gt;
        &lt;span class="na"&gt;proto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&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;Garantir que o Nginx está rodando e habilitado no boot&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;started&lt;/span&gt;
        &lt;span class="na"&gt;enabled&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;handlers&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;Recarregar Nginx&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reloaded&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O template referenciado usa variáveis Jinja2, o mesmo motor de templates usado internamente pelo Ansible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# templates/nginx-site.conf.j2&lt;/span&gt;
&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;inventory_hostname&lt;/span&gt; &lt;span class="err"&gt;}}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;app_port&lt;/span&gt; &lt;span class="err"&gt;}}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executando o playbook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; inventory/hosts.ini playbook-webserver.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note o uso de &lt;code&gt;notify&lt;/code&gt;/&lt;code&gt;handlers&lt;/code&gt;: a tarefa de copiar o template e a de habilitar o site &lt;strong&gt;notificam&lt;/strong&gt; o handler &lt;code&gt;Recarregar Nginx&lt;/code&gt;, que só roda uma vez ao final, mesmo que múltiplas tarefas o disparem — evitando reiniciar o serviço várias vezes desnecessariamente numa mesma execução.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Integrando Terraform e Ansible no mesmo fluxo
&lt;/h2&gt;

&lt;p&gt;Uma forma comum de conectar as duas ferramentas é usar o &lt;strong&gt;output&lt;/strong&gt; do Terraform para gerar o inventory do Ansible dinamicamente, em vez de manter os IPs hardcoded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# outputs.tf&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"web_ips"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[*].&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Gera o inventory a partir do output do Terraform&lt;/span&gt;
terraform output &lt;span class="nt"&gt;-json&lt;/span&gt; web_ips | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[]'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; inventory/hosts_dynamic.txt

ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; inventory/hosts_dynamic.txt playbook-webserver.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em pipelines mais maduros, isso costuma virar um plugin de inventory dinâmico (o Ansible tem plugins oficiais para AWS EC2, Azure, GCP, etc.), eliminando esse passo manual de exportar e converter outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. O Ansible pode substituir o Terraform?
&lt;/h2&gt;

&lt;p&gt;Tecnicamente, o Ansible tem módulos de cloud (&lt;code&gt;amazon.aws.ec2_instance&lt;/code&gt;, &lt;code&gt;azure.azcollection.azure_rm_virtualmachine&lt;/code&gt;, etc.) que conseguem criar recursos de infraestrutura, não só configurá-los. Na prática, isso é usado com menos frequência, por alguns motivos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O Terraform mantém um &lt;strong&gt;state&lt;/strong&gt; explícito e trata drift de forma mais robusta — ele sabe exatamente o que criou e detecta divergências. Módulos de cloud do Ansible tendem a ser mais imperativos, sem esse rastreamento nativo.&lt;/li&gt;
&lt;li&gt;O ecossistema de providers do Terraform (e OpenTofu) para APIs de nuvem é mais completo e atualizado que os módulos de cloud do Ansible.&lt;/li&gt;
&lt;li&gt;Misturar as responsabilidades — usar Ansible tanto para criar quanto para configurar — tende a deixar o pipeline mais difícil de raciocinar sobre "o que existe" versus "como está configurado".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por isso, a combinação mais comum no mercado continua sendo: Terraform (ou OpenTofu) provisiona, Ansible configura.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusão
&lt;/h2&gt;

&lt;p&gt;O Ansible não compete com o Terraform — ele resolve um problema diferente e complementar: o que fazer depois que a infraestrutura já existe. Inventory, playbooks, módulos e idempotência são os blocos fundamentais para automatizar de forma confiável a configuração de servidores, e a integração com o output do Terraform fecha o ciclo completo de provisionamento + configuração. No próximo e último artigo desta série, mudamos de assunto: como testar código Terraform antes de rodar &lt;code&gt;apply&lt;/code&gt; em produção, usando Terratest, checkov e tflint.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Terraform — &lt;a href="https://commons.wikimedia.org/wiki/File:Terraform-logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.ansible.com/" rel="noopener noreferrer"&gt;Ansible Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html" rel="noopener noreferrer"&gt;Ansible — Intro to Playbooks&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ansible</category>
      <category>terraform</category>
      <category>iac</category>
      <category>devops</category>
    </item>
    <item>
      <title>IaC além do Terraform - OpenTofu, o fork que virou alternativa séria</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Tue, 01 Sep 2026 09:30:51 +0000</pubDate>
      <link>https://dev.to/apsis-cc/iac-alem-do-terraform-opentofu-o-fork-que-virou-alternativa-seria-1fd8</link>
      <guid>https://dev.to/apsis-cc/iac-alem-do-terraform-opentofu-o-fork-que-virou-alternativa-seria-1fd8</guid>
      <description>&lt;h2&gt;
  
  
  1. Uma nova série: o mundo além do Terraform
&lt;/h2&gt;

&lt;p&gt;Na série anterior deste blog, exploramos em profundidade como combinar Terraform e YAML para gerenciar infraestrutura em múltiplos ambientes. O Terraform é, de longe, a ferramenta de Infraestrutura como Código (IaC) mais popular do mercado — mas está longe de ser a única peça relevante do ecossistema. Nesta nova série, vamos sair da bolha do Terraform puro e olhar para ferramentas que complementam (ou, em alguns casos, competem com) ele: OpenTofu, Ansible, e as ferramentas de teste que garantem que o código de infraestrutura realmente faz o que promete.&lt;/p&gt;

&lt;p&gt;Começamos pelo caso mais próximo de casa: o &lt;strong&gt;OpenTofu&lt;/strong&gt;, um fork direto do Terraform que nasceu de uma crise de licenciamento e hoje é um projeto da Linux Foundation com vida própria.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Por que um fork aconteceu
&lt;/h2&gt;

&lt;p&gt;Em agosto de 2023, a HashiCorp anunciou que todos os seus produtos, incluindo o Terraform, deixariam de usar a licença Mozilla Public License 2.0 (MPL 2.0) — uma licença open source reconhecida pela OSI (Open Source Initiative) — e passariam a usar a Business Source License (BSL) 1.1.&lt;/p&gt;

&lt;p&gt;Na prática, a BSL proíbe que terceiros usem o código da HashiCorp para criar produtos ou serviços que &lt;strong&gt;compitam diretamente&lt;/strong&gt; com os produtos da própria HashiCorp. Isso pegou em cheio empresas que ofereciam plataformas de gerenciamento de estado, automação de pipelines e "Terraform as a Service" construídas sobre o código aberto do Terraform — de repente, seus modelos de negócio ficaram juridicamente incertos.&lt;/p&gt;

&lt;p&gt;A reação da comunidade foi rápida. Em setembro de 2023, um grupo de empresas (incluindo Spacelift, Env0, Gruntwork, Harness e outras) anunciou a criação do &lt;strong&gt;OpenTF&lt;/strong&gt;, um fork do último release do Terraform sob a MPL 2.0 original — congelando o código no ponto exato antes da mudança de licença. Pouco depois, o projeto foi doado para a &lt;strong&gt;Linux Foundation&lt;/strong&gt; e renomeado para &lt;strong&gt;OpenTofu&lt;/strong&gt;, ganhando governança neutra e um roadmap independente da HashiCorp. [1]&lt;/p&gt;

&lt;h2&gt;
  
  
  3. O que é o OpenTofu, na prática
&lt;/h2&gt;

&lt;p&gt;OpenTofu não é um produto concorrente que reimplementa as ideias do Terraform do zero — é literalmente o mesmo código-fonte, mantido por uma comunidade aberta a partir do ponto em que divergiu. Isso significa que, para quem já usa Terraform, a curva de aprendizado para migrar é próxima de zero:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A sintaxe é &lt;strong&gt;HCL (HashiCorp Configuration Language)&lt;/strong&gt;, idêntica.&lt;/li&gt;
&lt;li&gt;Os providers (AWS, GCP, Azure, Kubernetes, etc.) continuam funcionando — o OpenTofu consome o mesmo Terraform Registry por padrão, além de manter seu próprio registry espelhado.&lt;/li&gt;
&lt;li&gt;O formato do arquivo de state é compatível.&lt;/li&gt;
&lt;li&gt;Os comandos são praticamente os mesmos, trocando o binário &lt;code&gt;terraform&lt;/code&gt; por &lt;code&gt;tofu&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tofu init
tofu plan
tofu apply
tofu destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Diferenças práticas que já existem hoje
&lt;/h2&gt;

&lt;p&gt;Apesar de compatível, o OpenTofu não ficou parado — o projeto já implementou funcionalidades que o Terraform ainda não tem (ou demorou mais para lançar):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Criptografia de state nativa (&lt;code&gt;state encryption&lt;/code&gt;):&lt;/strong&gt; o OpenTofu permite criptografar o arquivo de state em repouso e em trânsito usando chaves gerenciadas pelo usuário, sem depender de um backend que já faça isso (como um bucket S3 com criptografia server-side). Isso é configurado diretamente no bloco &lt;code&gt;terraform&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;encryption&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;key_provider&lt;/span&gt; &lt;span class="s2"&gt;"pbkdf2"&lt;/span&gt; &lt;span class="s2"&gt;"mykey"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;passphrase&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state_encryption_passphrase&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="s2"&gt;"aes_gcm"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pbkdf2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mykey&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aes_gcm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&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;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;for_each&lt;/code&gt; em blocos &lt;code&gt;provider&lt;/code&gt;:&lt;/strong&gt; permite instanciar múltiplas configurações de um mesmo provider dinamicamente (por exemplo, uma credencial AWS por conta em uma lista), algo que no Terraform exige declarar cada alias manualmente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Registry alternativo e distribuído:&lt;/strong&gt; o OpenTofu Registry funciona de forma independente do Terraform Registry, reduzindo o risco de um único ponto de falha para quem depende de providers e módulos públicos.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No dia a dia de um módulo Terraform comum — recursos, variáveis, outputs, &lt;code&gt;for_each&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;, módulos aninhados — não há diferença perceptível.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Quando faz sentido migrar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Preocupação com o modelo de licenciamento:&lt;/strong&gt; se sua empresa constrói produtos ou serviços sobre IaC (plataformas internas de self-service, ferramentas de automação) e quer evitar qualquer ambiguidade jurídica com a BSL, o OpenTofu remove essa preocupação por estar sob MPL 2.0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Você já usa Terraform CLI puro&lt;/strong&gt;, sem depender de recursos exclusivos do Terraform Cloud/Enterprise (como Sentinel para policy-as-code, ou Run Tasks). Nesse caso, a migração tende a ser praticamente transparente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Você quer as features que só existem no OpenTofu&lt;/strong&gt;, como a criptografia de state nativa mencionada acima.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Governança aberta é um critério de decisão&lt;/strong&gt; para sua organização — times que valorizam projetos sob fundações neutras (como já acontece com Kubernetes na CNCF) tendem a preferir o modelo do OpenTofu.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Quando NÃO faz sentido migrar (ainda)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependência forte do Terraform Cloud/Enterprise:&lt;/strong&gt; se sua equipe usa Sentinel, Run Tasks, ou outras integrações profundas com a plataforma paga da HashiCorp, migrar exige primeiro substituir essas peças por equivalentes (o OpenTofu tem parceiros como Spacelift, Env0 e Scalr oferecendo isso, mas é uma migração adicional).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Módulos ou providers de terceiros com licenciamento restritivo:&lt;/strong&gt; raro, mas alguns módulos privados podem ter cláusulas específicas sobre qual runtime é permitido.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero atrito no momento:&lt;/strong&gt; se o time está satisfeito com o Terraform atual e não há motivação de licenciamento, features ou governança, trocar de ferramenta só para trocar introduz risco operacional sem benefício claro. IaC não é o lugar para migrações por modismo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Migrando sem drama
&lt;/h2&gt;

&lt;p&gt;A migração de um projeto Terraform existente para OpenTofu costuma ser um processo de poucos passos, já que o formato de state é compatível:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Instala o tofu (exemplo via script oficial)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.opentofu.org/install-opentofu.sh | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--install-method&lt;/span&gt; standalone

&lt;span class="c"&gt;# Dentro do diretório do projeto Terraform existente&lt;/span&gt;
tofu init

&lt;span class="c"&gt;# Confirma que o plano bate com o que o terraform geraria&lt;/span&gt;
tofu plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Na maioria dos casos, &lt;code&gt;tofu init&lt;/code&gt; reconhece o state existente sem exigir nenhuma migração explícita — o formato é o mesmo. É recomendado rodar &lt;code&gt;tofu plan&lt;/code&gt; logo após a troca e comparar com o último &lt;code&gt;terraform plan&lt;/code&gt; para garantir que nenhuma mudança inesperada apareça antes de rodar &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusão
&lt;/h2&gt;

&lt;p&gt;O OpenTofu não é uma ferramenta radicalmente diferente do Terraform — e essa é exatamente a sua proposta de valor. Para quem já domina Terraform, migrar é uma decisão de baixo risco técnico e alto peso estratégico (licenciamento, governança, e algumas features exclusivas). Não é uma migração obrigatória para todo mundo, mas é uma alternativa madura o suficiente para estar na mesa de qualquer discussão sobre o futuro do seu stack de IaC. No próximo artigo desta série, saímos do território "provisionar infraestrutura" para entrar no de "configurar o que já foi provisionado": o Ansible.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Terraform — &lt;a href="https://commons.wikimedia.org/wiki/File:Terraform-logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://opentofu.org/docs/intro/" rel="noopener noreferrer"&gt;OpenTofu — About&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://search.opentofu.org/" rel="noopener noreferrer"&gt;OpenTofu Registry&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>opentofu</category>
      <category>terraform</category>
      <category>iac</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Fzf com Tmux - integração e pop-ups</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Wed, 26 Aug 2026 09:30:29 +0000</pubDate>
      <link>https://dev.to/apsis-cc/fzf-com-tmux-integracao-e-pop-ups-2cc7</link>
      <guid>https://dev.to/apsis-cc/fzf-com-tmux-integracao-e-pop-ups-2cc7</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: o que é o Fzf
&lt;/h2&gt;

&lt;p&gt;Na primeira parte desta série vimos o que é o fzf, como instalá-lo e como usá-lo direto no shell com &lt;code&gt;Ctrl+R&lt;/code&gt;, &lt;code&gt;Ctrl+T&lt;/code&gt; e &lt;code&gt;Alt+C&lt;/code&gt;. Quem também usa tmux no dia a dia ganha um segundo nível de integração: o fzf pode rodar dentro de janelas flutuantes (pop-ups) do próprio tmux, sem interferir no layout de painéis já aberto, e servir de seletor para operações do próprio tmux — trocar de sessão, de janela, de painel, matar processos em outro painel etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Por que integrar Fzf com Tmux
&lt;/h2&gt;

&lt;p&gt;Sem integração, usar o fzf dentro de uma sessão tmux funciona normalmente, mas cada busca ocupa o painel inteiro: se o objetivo é só escolher um arquivo ou trocar de branch rapidamente, o conteúdo do painel (um editor, um servidor rodando) é temporariamente coberto e é preciso "voltar" depois. Além disso, o tmux tem sua própria lista de coisas que fazem sentido filtrar de forma fuzzy — sessões, janelas, painéis — e não há um binding nativo do tmux para isso.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;fzf-tmux&lt;/code&gt;, incluído na instalação do fzf, resolve o primeiro problema: roda o fzf em uma janela sobreposta (pop-up ou split temporário) que desaparece assim que a seleção é feita, sem afetar o conteúdo do painel original. Combinado com bindings customizados no &lt;code&gt;tmux.conf&lt;/code&gt;, também resolve o segundo.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. fzf-tmux: pop-ups nativos
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;fzf-tmux&lt;/code&gt; é um wrapper de shell em torno do &lt;code&gt;fzf&lt;/code&gt; que aceita as mesmas opções, mais flags de posicionamento e tamanho da janela sobreposta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# pop-up centralizado, 80% da largura e 60% da altura do terminal&lt;/span&gt;
fzf-tmux &lt;span class="nt"&gt;-p&lt;/span&gt; 80%,60%

&lt;span class="c"&gt;# split na parte de baixo do painel atual, ocupando 40% da altura&lt;/span&gt;
fzf-tmux &lt;span class="nt"&gt;-d&lt;/span&gt; 40%

&lt;span class="c"&gt;# split lateral à direita, ocupando 50% da largura&lt;/span&gt;
fzf-tmux &lt;span class="nt"&gt;-d&lt;/span&gt; 50% &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A flag &lt;code&gt;-p&lt;/code&gt; (disponível a partir do tmux 3.2, que suporta &lt;code&gt;display-popup&lt;/code&gt;) é a mais usada hoje: cria uma janela verdadeiramente flutuante, sobreposta ao conteúdo do painel, que não reorganiza o layout existente — diferente do &lt;code&gt;-d&lt;/code&gt;, que faz um split real e temporariamente redistribui o espaço entre painéis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# substitui o Ctrl+T padrão (busca de arquivo) por um pop-up&lt;/span&gt;
vim &lt;span class="si"&gt;$(&lt;/span&gt;fzf-tmux &lt;span class="nt"&gt;-p&lt;/span&gt; 80%,60%&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Configurando pop-ups no &lt;code&gt;~/.tmux.conf&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;O ganho maior vem de amarrar &lt;code&gt;fzf-tmux&lt;/code&gt; a atalhos do próprio tmux, para que buscas fiquem a uma tecla de distância independente do que está rodando no painel atual. Alguns bindings úteis para adicionar ao &lt;code&gt;~/.tmux.conf&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;# trocar de sessão com busca fuzzy em pop-up
bind-key S display-popup -E "tmux list-sessions -F '#S' | fzf --reverse | xargs tmux switch-client -t"

# trocar de janela na sessão atual
bind-key W display-popup -E "tmux list-windows -F '#I: #W' | fzf --reverse | cut -d: -f1 | xargs tmux select-window -t"

# abrir um seletor de painéis de todas as sessões
bind-key P display-popup -E "tmux list-panes -a -F '#S:#I.#P #{pane_current_command}' | fzf --reverse | cut -d' ' -f1 | xargs tmux switch-client -t"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A flag &lt;code&gt;-E&lt;/code&gt; do &lt;code&gt;display-popup&lt;/code&gt; fecha o pop-up automaticamente assim que o comando termina — importante aqui, já que o próprio &lt;code&gt;fzf&lt;/code&gt; já trata &lt;code&gt;Esc&lt;/code&gt; como cancelamento e &lt;code&gt;Enter&lt;/code&gt; como confirmação; sem &lt;code&gt;-E&lt;/code&gt;, o pop-up ficaria aberto esperando mais um &lt;code&gt;Enter&lt;/code&gt; para fechar.&lt;/p&gt;

&lt;p&gt;Depois de editar o &lt;code&gt;tmux.conf&lt;/code&gt;, recarregue a configuração sem precisar reiniciar as sessões:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ctrl+b :source-file ~/.tmux.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Casos de uso práticos
&lt;/h2&gt;

&lt;p&gt;Com os bindings acima, o fluxo de trabalho muda de forma sutil mas sensível: em vez de &lt;code&gt;Ctrl+b s&lt;/code&gt; (lista de sessões nativa do tmux, sem busca) e navegar com as setas, &lt;code&gt;Ctrl+b S&lt;/code&gt; abre o pop-up já filtrável — digitar duas ou três letras do nome do projeto já é suficiente. O mesmo vale para &lt;code&gt;W&lt;/code&gt; (janelas) e &lt;code&gt;P&lt;/code&gt; (painéis entre sessões, útil quando há muitas sessões abertas simultaneamente, cada uma com vários painéis).&lt;/p&gt;

&lt;p&gt;Outro caso comum é buscar e matar um processo específico rodando em qualquer painel, sem precisar navegar até ele manualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bind-key K display-popup -E "ps aux | fzf --reverse --header='Matar processo' | awk '{print \$2}' | xargs -r kill -9"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E, claro, os usos do artigo anterior (buscar arquivo, trocar de branch, navegar no histórico) continuam funcionando igual dentro do tmux — a diferença é que, com &lt;code&gt;fzf-tmux -p&lt;/code&gt;, é possível trocar os bindings padrão do shell (&lt;code&gt;Ctrl+T&lt;/code&gt;, &lt;code&gt;Ctrl+R&lt;/code&gt;) para abrirem em pop-up em vez de ocupar o painel inteiro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# no .bashrc/.zshrc, quando dentro de uma sessão tmux&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FZF_TMUX_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-p 80%,60%"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Plugins úteis: tmux-fzf
&lt;/h2&gt;

&lt;p&gt;Para quem prefere não escrever os bindings à mão, o plugin &lt;a href="https://github.com/sainnhe/tmux-fzf" rel="noopener noreferrer"&gt;tmux-fzf&lt;/a&gt; empacota menus fuzzy prontos para sessões, janelas, painéis e até comandos do próprio tmux (matar sessão, renomear janela, mover painel), instalável via &lt;a href="https://github.com/tmux-plugins/tpm" rel="noopener noreferrer"&gt;TPM&lt;/a&gt; — o gerenciador de plugins citado na primeira parte desta série:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# no ~/.tmux.conf
set -g @plugin 'sainnhe/tmux-fzf'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois de instalar (&lt;code&gt;Ctrl+b I&lt;/code&gt; para o TPM buscar os plugins), o atalho padrão do plugin (&lt;code&gt;Ctrl+b F&lt;/code&gt; ou o prefixo configurado) abre um menu principal com todas as categorias — uma alternativa mais completa aos bindings manuais da seção anterior, ao custo de menos controle fino sobre o comportamento de cada um.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão
&lt;/h2&gt;

&lt;p&gt;Com o &lt;code&gt;fzf-tmux&lt;/code&gt; e os bindings de pop-up, a busca fuzzy deixa de ser algo que só acontece dentro do shell e passa a fazer parte da navegação do próprio tmux — trocar de sessão, janela ou painel vira tão rápido quanto digitar algumas letras, sem sair do teclado e sem perder de vista o que está rodando nos outros painéis. Combinados, os dois artigos desta série cobrem o suficiente para o fzf virar parte permanente do fluxo de trabalho no terminal, do shell puro até o multiplexador.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man1/tmux.1.html" rel="noopener noreferrer"&gt;tmux — display-popup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sainnhe/tmux-fzf" rel="noopener noreferrer"&gt;tmux-fzf — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>fzf</category>
      <category>cli</category>
      <category>tmux</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Fzf - o que é, como instalar e onde usar no dia a dia</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Mon, 24 Aug 2026 09:30:12 +0000</pubDate>
      <link>https://dev.to/apsis-cc/fzf-o-que-e-como-instalar-e-onde-usar-no-dia-a-dia-36oi</link>
      <guid>https://dev.to/apsis-cc/fzf-o-que-e-como-instalar-e-onde-usar-no-dia-a-dia-36oi</guid>
      <description>&lt;h2&gt;
  
  
  1. O problema que o Fzf resolve
&lt;/h2&gt;

&lt;p&gt;Quem vive no terminal conhece a cena: &lt;code&gt;Ctrl+R&lt;/code&gt; para buscar um comando no histórico, mas a busca é linear e só mostra um resultado por vez; &lt;code&gt;cd&lt;/code&gt; para um diretório profundo, mas é preciso lembrar (ou digitar) o caminho inteiro; &lt;code&gt;git checkout&lt;/code&gt; para uma branch, mas primeiro é necessário rodar &lt;code&gt;git branch&lt;/code&gt; e copiar o nome exato. Em todos esses casos, o gargalo é o mesmo: escolher um item entre muitos, digitando cada vez mais texto até sobrar só um.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;fzf&lt;/strong&gt; (fuzzy finder) resolve isso de um jeito genérico: ele pega qualquer lista de linhas — histórico de comandos, arquivos, branches, processos, o que for — e transforma essa lista em um filtro interativo, digitado em tempo real, onde não é preciso acertar a grafia exata nem a ordem das letras. Basta digitar pedaços do que se lembra e o fzf ordena os resultados por relevância.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. O que é o Fzf
&lt;/h2&gt;

&lt;p&gt;Fzf é um filtro de linha de comando escrito em Go, de código aberto, mantido por Junegunn Choi. Ele não sabe nada sobre arquivos, git ou processos — a única coisa que ele faz é ler linhas da entrada padrão (&lt;code&gt;stdin&lt;/code&gt;) e devolver, na saída padrão (&lt;code&gt;stdout&lt;/code&gt;), a linha (ou linhas) selecionada interativamente. Essa simplicidade é o que o torna tão versátil: qualquer comando que produza uma lista de texto pode ser "encanado" (&lt;code&gt;|&lt;/code&gt;) para dentro do fzf.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# a ideia básica: qualquer lista vira um menu interativo&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; | fzf
&lt;span class="nb"&gt;history&lt;/span&gt; | fzf
git branch | fzf
ps aux | fzf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Na prática, o fzf raramente é usado sozinho dessa forma — o valor real aparece quando ele é integrado ao shell e a outras ferramentas, o que este artigo cobre a partir da próxima seção.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Instalando o Fzf
&lt;/h2&gt;

&lt;p&gt;O fzf está disponível nos principais gerenciadores de pacote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;fzf

&lt;span class="c"&gt;# Fedora&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;fzf

&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; fzf

&lt;span class="c"&gt;# macOS (Homebrew)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;fzf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também é possível instalar via git, o que traz um script auxiliar de configuração dos atalhos de shell (usados na próxima seção):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O instalador pergunta se deseja habilitar os atalhos de autocompletar e os bindings de tecla — vale responder "sim" às duas perguntas, é exatamente o que torna o fzf útil no dia a dia. Depois de instalado, confirme a versão:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fzf &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Integração com o shell: os três atalhos essenciais
&lt;/h2&gt;

&lt;p&gt;A maior parte do valor do fzf no terminal vem de três atalhos de teclado que ele registra no shell (bash, zsh ou fish) depois de configurado:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Atalho&lt;/th&gt;
&lt;th&gt;Ação&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Busca fuzzy no histórico de comandos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Insere o caminho de um arquivo/diretório selecionado na linha&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt+C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Muda (&lt;code&gt;cd&lt;/code&gt;) para um diretório selecionado, buscando abaixo do atual&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Ctrl+R&lt;/code&gt; substitui a busca reversa padrão do shell (que já usa &lt;code&gt;Ctrl+R&lt;/code&gt;, mas de forma linear e sem preview) por uma lista filtrável de todo o histórico, digitando qualquer parte do comando lembrado. &lt;code&gt;Ctrl+T&lt;/code&gt;, no meio de qualquer comando, abre uma busca de arquivos a partir do diretório atual e insere o caminho escolhido na posição do cursor — útil para não precisar digitar caminhos longos por completo. &lt;code&gt;Alt+C&lt;/code&gt; faz o mesmo, mas para navegar diretamente para dentro de um diretório.&lt;/p&gt;

&lt;p&gt;Se a instalação via &lt;code&gt;apt&lt;/code&gt;/&lt;code&gt;dnf&lt;/code&gt;/&lt;code&gt;pacman&lt;/code&gt; não habilitou esses atalhos automaticamente, adicione ao &lt;code&gt;.bashrc&lt;/code&gt; ou &lt;code&gt;.zshrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# bash&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /usr/share/doc/fzf/examples/key-bindings.bash &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;source&lt;/span&gt; /usr/share/doc/fzf/examples/key-bindings.bash
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /usr/share/doc/fzf/examples/completion.bash &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;source&lt;/span&gt; /usr/share/doc/fzf/examples/completion.bash

&lt;span class="c"&gt;# zsh&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; /usr/share/doc/fzf/examples/key-bindings.zsh
&lt;span class="nb"&gt;source&lt;/span&gt; /usr/share/doc/fzf/examples/completion.zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(O caminho exato dos exemplos varia por distribuição; &lt;code&gt;~/.fzf/install&lt;/code&gt; resolve isso automaticamente.)&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Usos no dia a dia
&lt;/h2&gt;

&lt;p&gt;Além dos três atalhos, o fzf combina bem com comandos que já fazem parte da rotina, encanando a saída deles para dentro do filtro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# trocar de branch git interativamente&lt;/span&gt;
git checkout &lt;span class="si"&gt;$(&lt;/span&gt;git branch &lt;span class="nt"&gt;--all&lt;/span&gt; | fzf | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'* '&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# matar um processo escolhendo da lista&lt;/span&gt;
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;ps aux | fzf | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# abrir um arquivo no editor, buscando pelo nome&lt;/span&gt;
vim &lt;span class="si"&gt;$(&lt;/span&gt;fzf&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# buscar e copiar um commit específico do log&lt;/span&gt;
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; | fzf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esses exemplos funcionam porque &lt;code&gt;$(comando | fzf)&lt;/code&gt; captura a linha selecionada e a devolve como argumento — o mesmo padrão se aplica a praticamente qualquer ferramenta de linha de comando que aceite um nome ou identificador como argumento.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Customizando o Fzf: comando padrão e preview
&lt;/h2&gt;

&lt;p&gt;Por padrão, &lt;code&gt;Ctrl+T&lt;/code&gt; e &lt;code&gt;fzf&lt;/code&gt; sozinho usam &lt;code&gt;find&lt;/code&gt; para listar arquivos, o que inclui diretórios como &lt;code&gt;.git&lt;/code&gt; e &lt;code&gt;node_modules&lt;/code&gt; e pode ser lento em projetos grandes. É comum substituir isso por uma ferramenta mais rápida, como &lt;code&gt;fd&lt;/code&gt; ou &lt;code&gt;rg --files&lt;/code&gt;, via a variável &lt;code&gt;FZF_DEFAULT_COMMAND&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# no .bashrc/.zshrc&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FZF_DEFAULT_COMMAND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'fd --type f --hidden --exclude .git'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FZF_CTRL_T_COMMAND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FZF_DEFAULT_COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outro recurso muito usado é o preview, que mostra o conteúdo do item selecionado em um painel lateral enquanto se navega pela lista:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# preview de arquivos com bat (syntax highlighting) ao usar Ctrl+T&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FZF_CTRL_T_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--preview 'bat --color=always --line-range :50 {}'"&lt;/span&gt;

&lt;span class="c"&gt;# preview do diff ao buscar branches&lt;/span&gt;
git branch | fzf &lt;span class="nt"&gt;--preview&lt;/span&gt; &lt;span class="s1"&gt;'git log --oneline --graph --color=always {1} | head -20'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Nesta primeira parte, vimos o problema que o fzf resolve, como instalá-lo, os três atalhos de shell que fazem a maior diferença no dia a dia (&lt;code&gt;Ctrl+R&lt;/code&gt;, &lt;code&gt;Ctrl+T&lt;/code&gt;, &lt;code&gt;Alt+C&lt;/code&gt;) e como combiná-lo com outros comandos e personalizar seu comportamento. No próximo artigo, o foco é a integração do fzf com o tmux: como abrir buscas em pop-ups nativos, trocar de sessão ou painel sem sair do teclado, e os plugins que tornam esse combo ainda mais produtivo.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/junegunn/fzf/wiki/examples" rel="noopener noreferrer"&gt;fzf — Wiki de exemplos&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>fzf</category>
      <category>cli</category>
      <category>tmux</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Docker avançado - multi-stage builds, segurança e CI/CD</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Mon, 17 Aug 2026 09:30:12 +0000</pubDate>
      <link>https://dev.to/apsis-cc/docker-avancado-multi-stage-builds-seguranca-e-cicd-113j</link>
      <guid>https://dev.to/apsis-cc/docker-avancado-multi-stage-builds-seguranca-e-cicd-113j</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: da aplicação funcionando ao container pronto para produção
&lt;/h2&gt;

&lt;p&gt;Esta série cobriu, até aqui, o suficiente para desenvolver com Docker no dia a dia: conceitos fundamentais, comandos essenciais, Dockerfiles eficientes, rede, volumes e Compose para orquestrar múltiplos serviços. Este último artigo fecha a lacuna entre "funciona no meu Compose local" e "pronto para rodar em produção": imagens menores via multi-stage builds, segurança básica e não negociável, e como tudo isso se integra a um pipeline de CI/CD.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. O problema que multi-stage builds resolve
&lt;/h2&gt;

&lt;p&gt;Compilar ou empacotar uma aplicação frequentemente exige ferramentas que a aplicação &lt;strong&gt;não precisa em tempo de execução&lt;/strong&gt;: compiladores, headers de desenvolvimento, o próprio código-fonte antes de ser transpilado/buildado. Um Dockerfile ingênuo carrega tudo isso para a imagem final:&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="c"&gt;# Ruim: ferramentas de build viajam junto para produção&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa imagem inclui o &lt;code&gt;npm&lt;/code&gt;, todo o &lt;code&gt;node_modules&lt;/code&gt; (incluindo dependências de desenvolvimento), o código-fonte original e as ferramentas de build — frequentemente centenas de MBs de peso morto que nunca são usados depois que &lt;code&gt;npm run build&lt;/code&gt; termina, e que ainda aumentam a superfície de ataque da imagem (mais binários, mais coisa que pode ter vulnerabilidade).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-stage builds&lt;/strong&gt; resolvem isso permitindo múltiplos blocos &lt;code&gt;FROM&lt;/code&gt; no mesmo Dockerfile, onde estágios posteriores copiam seletivamente apenas o que precisam dos anteriores — o restante do estágio de build simplesmente não existe na imagem final:&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="c"&gt;# Estágio 1: build, com todas as ferramentas necessárias&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20&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;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Estágio 2: produção, só com o resultado do build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A imagem final não contém o código-fonte original, nem as ferramentas de build do primeiro estágio — só o &lt;code&gt;dist/&lt;/code&gt; compilado e as dependências necessárias em runtime. A redução de tamanho costuma ser dramática: não é incomum cair de mais de 1 GB para menos de 150 MB na mesma aplicação.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Multi-stage com linguagens compiladas
&lt;/h2&gt;

&lt;p&gt;O ganho é ainda mais evidente com linguagens compiladas, onde o binário final não precisa nem do compilador nem do código-fonte:&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="c"&gt;# Estágio 1: compila o binário&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.22&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;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; servidor .

&lt;span class="c"&gt;# Estágio 2: só o binário, em uma imagem praticamente vazia&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/servidor /servidor&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/servidor"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;scratch&lt;/code&gt; é uma imagem literalmente vazia — sem shell, sem libc, sem utilitário algum. Um binário Go estaticamente linkado (&lt;code&gt;CGO_ENABLED=0&lt;/code&gt;) não precisa de nada disso, resultando em uma imagem final de poucos MBs. Isso também é uma vantagem de segurança: uma imagem sem shell nem utilitários tem uma superfície de ataque drasticamente menor, já que não há ferramentas para um invasor usar mesmo que consiga executar algo dentro do container.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Segurança: nunca rodar como root
&lt;/h2&gt;

&lt;p&gt;Por padrão, um container roda seus processos como &lt;strong&gt;root&lt;/strong&gt;, a menos que a imagem base ou o Dockerfile digam o contrário. Isso é um risco real: se uma vulnerabilidade na aplicação permitir execução de código arbitrário dentro do container, esse código roda como root — e embora o isolamento de namespaces limite o dano ao host na maioria dos casos, root dentro do container ainda tem acesso irrestrito ao sistema de arquivos e processos daquele container, e certas vulnerabilidades de escape de container (historicamente raras, mas existentes) dependem exatamente de o processo estar rodando como root.&lt;/p&gt;

&lt;p&gt;A correção é criar e usar um usuário sem privilégios no Dockerfile:&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="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;--create-home&lt;/span&gt; appuser
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appuser . .&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python3", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A instrução &lt;code&gt;USER appuser&lt;/code&gt; garante que, a partir dali, todo comando do Dockerfile e todo processo do container em execução roda com esse usuário — não root. Muitas imagens oficiais já vêm com um usuário não-root pronto para uso (por exemplo, &lt;code&gt;node&lt;/code&gt; tem o usuário &lt;code&gt;node&lt;/code&gt;), bastando declarar &lt;code&gt;USER node&lt;/code&gt; em vez de criar um novo.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Scan de vulnerabilidades
&lt;/h2&gt;

&lt;p&gt;Mesmo com boas práticas de Dockerfile, a imagem base e as dependências instaladas podem conter vulnerabilidades conhecidas (CVEs) em bibliotecas do sistema. Ferramentas de scan verificam a imagem construída contra bancos de dados de vulnerabilidades conhecidas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Docker Scout, integrado ao Docker CLI&lt;/span&gt;
docker scout cves minha-app:1.0

&lt;span class="c"&gt;# Trivy, alternativa open-source amplamente usada&lt;/span&gt;
trivy image minha-app:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A saída lista pacotes com vulnerabilidades conhecidas, sua severidade (baixa, média, alta, crítica) e, quando disponível, a versão que corrige o problema — frequentemente resolvido apenas atualizando a imagem base ou uma dependência específica. Rodar esse scan como parte do pipeline de CI (a seguir) evita que uma imagem com vulnerabilidade crítica conhecida chegue a produção sem que ninguém tenha visto o alerta.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Docker em pipelines de CI/CD
&lt;/h2&gt;

&lt;p&gt;O fluxo mais comum em CI é: build da imagem, rodar testes dentro dela (ou contra ela), scan de segurança, e — só se tudo passar — push para o registry e deploy. Um exemplo de pipeline (sintaxe do GitHub Actions, mas o fluxo é o mesmo em qualquer ferramenta de CI):&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="c1"&gt;# .github/workflows/build.yml&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;build-and-push&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&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="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Build da imagem&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build -t minha-app:${{ github.sha }} .&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;Scan de vulnerabilidades&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker scout cves minha-app:${{ github.sha }} --exit-code --only-severity critical,high&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;Login no registry&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "${{ secrets.REGISTRY_TOKEN }}" | docker login registry.exemplo.com -u usuario --password-stdin&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;Push da imagem&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;docker tag minha-app:${{ github.sha }} registry.exemplo.com/minha-app:${{ github.sha }}&lt;/span&gt;
          &lt;span class="s"&gt;docker push registry.exemplo.com/minha-app:${{ github.sha }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Duas práticas valem destaque nesse pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tag pela SHA do commit&lt;/strong&gt; (&lt;code&gt;${{ github.sha }}&lt;/code&gt;), não &lt;code&gt;latest&lt;/code&gt; — cada build gera uma imagem rastreável a um commit exato, essencial para saber o que está rodando em produção e para reverter (&lt;code&gt;rollback&lt;/code&gt;) para uma versão anterior com confiança.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--exit-code&lt;/code&gt; no scan&lt;/strong&gt; — faz o comando retornar código de saída diferente de zero se encontrar vulnerabilidade crítica/alta, o que interrompe o pipeline automaticamente em vez de depender de alguém ler o relatório manualmente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O cache de build (Artigo 3 desta série) também importa em CI: runners efêmeros normalmente começam sem cache local, então ferramentas como &lt;code&gt;docker buildx&lt;/code&gt; com cache remoto (&lt;code&gt;--cache-from&lt;/code&gt;/&lt;code&gt;--cache-to&lt;/code&gt; apontando para o registry) evitam que cada execução do pipeline reconstrua tudo do zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão da série
&lt;/h2&gt;

&lt;p&gt;Ao longo desses seis artigos, esta série foi do "o que é um container" até um pipeline de CI/CD publicando imagens seguras e enxutas: o problema que o Docker resolve e seus conceitos fundamentais, os comandos do dia a dia, como escrever Dockerfiles eficientes aproveitando cache de build, como containers se comunicam via rede e persistem dados via volumes, como Compose orquestra tudo isso localmente, e finalmente como levar isso a produção com multi-stage builds, containers não-root e scan de vulnerabilidades integrado ao pipeline. A partir daqui, a base está posta para explorar orquestração em escala (Kubernetes) e tópicos mais específicos de infraestrutura — mas o Docker isolado, bem usado, já resolve a enorme maioria dos casos do dia a dia de desenvolvimento e deploy de aplicações.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Docker — &lt;a href="https://commons.wikimedia.org/wiki/File:Docker_Logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;, fonte: docker.com/company/newsroom/media-resources&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/build/building/multi-stage/" rel="noopener noreferrer"&gt;Docker Documentation — Multi-stage builds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/scout/" rel="noopener noreferrer"&gt;Docker Documentation — Docker Scout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP Docker Security Cheat Sheet&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Docker Compose - orquestrando múltiplos containers</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Sun, 16 Aug 2026 09:30:13 +0000</pubDate>
      <link>https://dev.to/apsis-cc/docker-compose-orquestrando-multiplos-containers-27hd</link>
      <guid>https://dev.to/apsis-cc/docker-compose-orquestrando-multiplos-containers-27hd</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: do &lt;code&gt;docker run&lt;/code&gt; repetido a um arquivo único
&lt;/h2&gt;

&lt;p&gt;No artigo anterior, subir uma API e um Postgres conectados exigiu dois comandos &lt;code&gt;docker run&lt;/code&gt; longos, com flags de rede, volume e variáveis de ambiente para lembrar (e digitar) toda vez. Em um projeto real, com mais serviços — cache, fila, worker em background — isso rapidamente vira inviável de manter na cabeça ou em um script solto. O &lt;strong&gt;Docker Compose&lt;/strong&gt; resolve isso descrevendo toda a aplicação multi-container em um único arquivo declarativo, versionado junto com o código.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. O arquivo &lt;code&gt;compose.yaml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Compose lê um arquivo YAML (por convenção &lt;code&gt;compose.yaml&lt;/code&gt;, ou o nome legado &lt;code&gt;docker-compose.yml&lt;/code&gt;, ainda amplamente usado) descrevendo &lt;strong&gt;serviços&lt;/strong&gt; (cada um vira um ou mais containers), redes e volumes:&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="c1"&gt;# compose.yaml&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;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;8000:8000"&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;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://postgres:segredo@banco:5432/postgres&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;banco&lt;/span&gt;

  &lt;span class="na"&gt;banco&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:16&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;segredo&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;pg-dados:/var/lib/postgresql/data&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;pg-dados&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso substitui inteiramente os dois &lt;code&gt;docker run&lt;/code&gt; do artigo anterior. Uma diferença importante já aparece aqui: &lt;strong&gt;por padrão, Compose cria uma rede própria para o projeto&lt;/strong&gt; e conecta todos os serviços a ela automaticamente — não é preciso um &lt;code&gt;docker network create&lt;/code&gt; manual, nem declarar &lt;code&gt;--network&lt;/code&gt; em cada serviço. Cada serviço já é acessível pelos demais pelo nome declarado em &lt;code&gt;services:&lt;/code&gt; (aqui, &lt;code&gt;banco&lt;/code&gt; resolve para o container do Postgres), exatamente como as redes definidas pelo usuário do artigo anterior.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Comandos essenciais do Compose
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;          &lt;span class="c"&gt;# sobe todos os serviços em segundo plano&lt;/span&gt;
docker compose ps              &lt;span class="c"&gt;# lista os containers do projeto e seu status&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; api     &lt;span class="c"&gt;# segue os logs de um serviço específico&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;         &lt;span class="c"&gt;# segue os logs de todos os serviços, intercalados&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;api bash   &lt;span class="c"&gt;# abre um shell dentro do container de um serviço&lt;/span&gt;
docker compose stop            &lt;span class="c"&gt;# para os containers sem removê-los&lt;/span&gt;
docker compose down            &lt;span class="c"&gt;# para e remove containers e rede (volumes nomeados sobrevivem)&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt;         &lt;span class="c"&gt;# remove também os volumes — cuidado, apaga dados&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O padrão &lt;code&gt;docker compose &amp;lt;comando&amp;gt; &amp;lt;serviço&amp;gt;&lt;/code&gt; se repete: a maioria dos comandos aceita opcionalmente o nome de um serviço específico (como em &lt;code&gt;logs -f api&lt;/code&gt;) ou afeta o projeto inteiro se omitido.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;depends_on&lt;/code&gt;: ordem de início, não prontidão
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;depends_on&lt;/code&gt; controla a &lt;strong&gt;ordem&lt;/strong&gt; em que os containers são criados e iniciados — no exemplo acima, &lt;code&gt;banco&lt;/code&gt; inicia antes de &lt;code&gt;api&lt;/code&gt;. O que ele &lt;strong&gt;não&lt;/strong&gt; garante é que o Postgres já esteja pronto para aceitar conexões nesse momento: o processo do Postgres pode levar alguns segundos para inicializar depois que o container começa a rodar, e uma API que tenta conectar imediatamente pode falhar nessa janela.&lt;/p&gt;

&lt;p&gt;A forma correta de esperar prontidão de verdade é um &lt;strong&gt;healthcheck&lt;/strong&gt;:&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;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;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;banco&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;

  &lt;span class="na"&gt;banco&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:16&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;segredo&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com &lt;code&gt;condition: service_healthy&lt;/code&gt;, o Compose só considera &lt;code&gt;banco&lt;/code&gt; "pronto" (e só então inicia &lt;code&gt;api&lt;/code&gt;) depois que o healthcheck passar — muito mais confiável do que assumir um tempo fixo de espera ou implementar retry manual na aplicação (embora ter retry na aplicação continue sendo uma boa prática defensiva, mesmo com healthcheck).&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Variáveis de ambiente e o arquivo &lt;code&gt;.env&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Segredos e configuração que variam por ambiente (senha de banco, chaves de API) não deveriam ficar hardcoded no &lt;code&gt;compose.yaml&lt;/code&gt;. Compose lê automaticamente um arquivo &lt;code&gt;.env&lt;/code&gt; no mesmo diretório e substitui variáveis referenciadas com &lt;code&gt;${NOME}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;segredo-local
&lt;span class="nv"&gt;API_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;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;${API_PORT}:8000"&lt;/span&gt;
  &lt;span class="na"&gt;banco&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:16&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;${POSTGRES_PASSWORD}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso permite versionar o &lt;code&gt;compose.yaml&lt;/code&gt; normalmente enquanto o &lt;code&gt;.env&lt;/code&gt; (que contém segredos reais) fica de fora do controle de versão via &lt;code&gt;.gitignore&lt;/code&gt; — o mesmo princípio de nunca commitar credenciais, aplicado à configuração do Compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Um ambiente completo: app + banco + cache
&lt;/h2&gt;

&lt;p&gt;Estendendo o exemplo para um cenário mais realista, com Redis como cache além da API e do Postgres:&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;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;8000:8000"&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;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://postgres:${POSTGRES_PASSWORD}@banco:5432/postgres&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://cache:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;banco&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_started&lt;/span&gt;

  &lt;span class="na"&gt;banco&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:16&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;${POSTGRES_PASSWORD}&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;pg-dados:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="na"&gt;cache&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;redis:7-alpine&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;redis-dados:/data&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;pg-dados&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis-dados&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um &lt;code&gt;docker compose up -d&lt;/code&gt; sobe os três serviços, na ordem certa, já conectados entre si e com dados persistentes — o equivalente a uma infraestrutura local completa de desenvolvimento, reproduzível em qualquer máquina que tenha Docker instalado, sem instalar Postgres ou Redis diretamente no sistema.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Escalando um serviço
&lt;/h2&gt;

&lt;p&gt;Para rodar múltiplas instâncias do mesmo serviço (por exemplo, vários workers processando uma fila em paralelo):&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;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--scale&lt;/span&gt; &lt;span class="nv"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso só funciona sem conflito para serviços que não publicam uma porta fixa do host (&lt;code&gt;ports:&lt;/code&gt;) — três containers não podem, todos, mapear a porta 8000 do host simultaneamente. Serviços pensados para escalar dessa forma tipicamente não expõem porta ao host diretamente, sendo acessados por outro serviço (como um proxy reverso) através da rede interna do Compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Docker Compose transforma uma coleção de comandos &lt;code&gt;docker run&lt;/code&gt; frágeis e difíceis de lembrar em um arquivo único, declarativo e versionável, que descreve toda a aplicação — serviços, rede, volumes, ordem de inicialização e variáveis de ambiente. No último artigo desta série, o assunto vai para práticas de nível de produção: multi-stage builds para reduzir drasticamente o tamanho final da imagem, segurança (rodar sem root, scan de vulnerabilidades) e como tudo isso se encaixa em um pipeline de CI/CD.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Docker — &lt;a href="https://commons.wikimedia.org/wiki/File:Docker_Logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;, fonte: docker.com/company/newsroom/media-resources&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Documentation — Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/reference/compose-file/" rel="noopener noreferrer"&gt;Compose File Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Docker - redes e volumes na prática</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Sat, 15 Aug 2026 09:30:21 +0000</pubDate>
      <link>https://dev.to/apsis-cc/docker-redes-e-volumes-na-pratica-1n0b</link>
      <guid>https://dev.to/apsis-cc/docker-redes-e-volumes-na-pratica-1n0b</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: de imagens bem construídas a containers que conversam entre si
&lt;/h2&gt;

&lt;p&gt;Os artigos anteriores desta série cobriram como criar imagens eficientes e rodar containers isolados. Mas uma aplicação real raramente é um único container: normalmente há uma API, um banco de dados, um cache, talvez uma fila de mensagens — cada um em seu próprio container, precisando se comunicar. E containers, por padrão, são efêmeros: qualquer dado escrito dentro deles some quando são removidos. Este artigo cobre as duas peças que resolvem isso: &lt;strong&gt;redes&lt;/strong&gt; (comunicação entre containers) e &lt;strong&gt;volumes&lt;/strong&gt; (persistência de dados).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. O problema do isolamento de rede por padrão
&lt;/h2&gt;

&lt;p&gt;Cada container recebe seu próprio namespace de rede, isolado dos demais e do host. Isso é uma característica de segurança, não um bug — mas significa que dois containers rodados de forma independente não conseguem se encontrar automaticamente:&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 &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; api minha-api
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; banco postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;De dentro do container &lt;code&gt;api&lt;/code&gt;, tentar acessar &lt;code&gt;banco&lt;/code&gt; por esse nome simplesmente falha — cada container, isolado, só enxerga &lt;code&gt;localhost&lt;/code&gt; como a si mesmo. A solução do Docker para isso é criar uma &lt;strong&gt;rede&lt;/strong&gt; e conectar ambos os containers a ela.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Redes definidas pelo usuário (User-Defined Networks)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create minha-rede

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; banco &lt;span class="nt"&gt;--network&lt;/span&gt; minha-rede postgres
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; api &lt;span class="nt"&gt;--network&lt;/span&gt; minha-rede minha-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A partir daqui, dentro do container &lt;code&gt;api&lt;/code&gt;, o hostname &lt;code&gt;banco&lt;/code&gt; resolve automaticamente para o IP do container &lt;code&gt;banco&lt;/code&gt; — o Docker roda um DNS interno para qualquer rede definida pelo usuário, resolvendo containers pelo nome (ou pelo alias definido com &lt;code&gt;--network-alias&lt;/code&gt;, se houver mais de um). Isso é o motivo pelo qual strings de conexão em aplicações containerizadas costumam usar o nome do serviço em vez de um IP fixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;=&lt;span class="n"&gt;postgresql&lt;/span&gt;://&lt;span class="n"&gt;usuario&lt;/span&gt;:&lt;span class="n"&gt;senha&lt;/span&gt;@&lt;span class="n"&gt;banco&lt;/span&gt;:&lt;span class="m"&gt;5432&lt;/span&gt;/&lt;span class="n"&gt;meudb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comandos úteis para inspecionar redes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network &lt;span class="nb"&gt;ls&lt;/span&gt;                  &lt;span class="c"&gt;# lista todas as redes&lt;/span&gt;
docker network inspect minha-rede  &lt;span class="c"&gt;# detalhes: containers conectados, subnet, gateway&lt;/span&gt;
docker network connect minha-rede outro-container   &lt;span class="c"&gt;# conecta um container já em execução&lt;/span&gt;
docker network disconnect minha-rede outro-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Os drivers de rede mais comuns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;bridge&lt;/strong&gt; (padrão): cada container recebe um IP interno numa sub-rede privada isolada; é o driver usado nos exemplos acima e o adequado para a grande maioria dos casos de uma única máquina.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;host&lt;/strong&gt;: o container compartilha diretamente a stack de rede do host, sem isolamento — usado quando a latência extra do NAT do bridge é inaceitável, ao custo de perder o isolamento de portas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;none&lt;/strong&gt;: desabilita rede completamente — útil para containers que só processam dados locais e não devem ter acesso externo algum, por segurança.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;overlay&lt;/strong&gt;: conecta containers rodando em &lt;strong&gt;hosts diferentes&lt;/strong&gt;, usado em clusters multi-máquina (Docker Swarm, e conceito similar ao que o Kubernetes resolve à sua própria maneira) — fora do escopo de uma única máquina, mas bom saber que existe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para a maioria dos projetos rodando em uma única máquina — o caso coberto por esta série — bridge com redes definidas pelo usuário é a resposta certa quase sempre.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Volumes: persistindo dados além da vida do container
&lt;/h2&gt;

&lt;p&gt;Por padrão, tudo que um processo escreve dentro de um container vive na camada gravável dele — e essa camada é destruída junto com o container quando ele é removido (&lt;code&gt;docker rm&lt;/code&gt;). Para um banco de dados, isso é inaceitável: perder todos os dados a cada &lt;code&gt;docker rm banco&lt;/code&gt; não é uma opção. A solução são &lt;strong&gt;volumes&lt;/strong&gt;, que existem fora do ciclo de vida de qualquer container específico.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create dados-banco

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; banco &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; dados-banco:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
  postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo removendo e recriando o container &lt;code&gt;banco&lt;/code&gt;, os dados em &lt;code&gt;dados-banco&lt;/code&gt; sobrevivem — um novo container apontando para o mesmo volume enxerga exatamente os mesmos dados de antes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop banco &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;rm &lt;/span&gt;banco
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; banco &lt;span class="nt"&gt;-v&lt;/span&gt; dados-banco:/var/lib/postgresql/data postgres
&lt;span class="c"&gt;# os dados anteriores continuam lá&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comandos de gerenciamento:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume &lt;span class="nb"&gt;ls&lt;/span&gt;                   &lt;span class="c"&gt;# lista volumes&lt;/span&gt;
docker volume inspect dados-banco  &lt;span class="c"&gt;# onde o volume vive no host, entre outros metadados&lt;/span&gt;
docker volume &lt;span class="nb"&gt;rm &lt;/span&gt;dados-banco       &lt;span class="c"&gt;# remove (falha se algum container ainda o usa)&lt;/span&gt;
docker volume prune                &lt;span class="c"&gt;# remove todos os volumes não usados por nenhum container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Volumes nomeados vs bind mounts
&lt;/h2&gt;

&lt;p&gt;Existem duas formas de "montar" algo do host dentro de um container, e a diferença importa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Volume nomeado&lt;/strong&gt; (&lt;code&gt;-v dados-banco:/caminho&lt;/code&gt;): gerenciado inteiramente pelo Docker, que decide onde no host os dados ficam de fato armazenados. É a opção certa para dados que uma aplicação gera e consome (bancos de dados, filas, caches persistentes) — portátil entre ambientes, porque não depende de um caminho específico do host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bind mount&lt;/strong&gt; (&lt;code&gt;-v /caminho/no/host:/caminho/no/container&lt;/code&gt;): aponta diretamente para um caminho existente do sistema de arquivos do host. É a opção certa para desenvolvimento local — montar o código-fonte do projeto dentro do container para ver mudanças refletidas sem rebuildar a imagem a cada alteração:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Desenvolvimento: código do host refletido ao vivo dentro do container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/src:/app/src &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 minha-app-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bind mounts não são recomendados para dados de produção porque acoplam o container a um caminho específico do host, o que quebra a portabilidade que o Docker existe para garantir.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Um cenário completo: API + banco comunicando-se com dados persistentes
&lt;/h2&gt;

&lt;p&gt;Juntando rede e volume no mesmo exemplo — uma API e um Postgres, isolados em rede própria, com dados que sobrevivem a reinicializações:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create app-rede
docker volume create pg-dados

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; banco &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; app-rede &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; pg-dados:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;segredo &lt;span class="se"&gt;\&lt;/span&gt;
  postgres:16

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; app-rede &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql://postgres:segredo@banco:5432/postgres &lt;span class="se"&gt;\&lt;/span&gt;
  minha-api:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse é exatamente o tipo de configuração que, no próximo artigo, o Docker Compose substitui por um único arquivo declarativo — em vez de dois comandos &lt;code&gt;docker run&lt;/code&gt; longos para lembrar (e digitar) toda vez.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Redes definidas pelo usuário resolvem a comunicação entre containers via DNS interno pelo nome, e volumes resolvem a persistência de dados além do ciclo de vida de qualquer container individual — as duas peças que faltavam para modelar uma aplicação real com múltiplos serviços. No próximo artigo, o Docker Compose entra em cena para descrever tudo isso — rede, volumes e múltiplos containers — em um único arquivo declarativo, versionável junto com o código.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Docker — &lt;a href="https://commons.wikimedia.org/wiki/File:Docker_Logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;, fonte: docker.com/company/newsroom/media-resources&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/network/" rel="noopener noreferrer"&gt;Docker Documentation — Networking Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/storage/volumes/" rel="noopener noreferrer"&gt;Docker Documentation — Volumes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Dockerfile na prática - camadas, cache de build e boas práticas</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Fri, 14 Aug 2026 09:30:11 +0000</pubDate>
      <link>https://dev.to/apsis-cc/dockerfile-na-pratica-camadas-cache-de-build-e-boas-praticas-3n80</link>
      <guid>https://dev.to/apsis-cc/dockerfile-na-pratica-camadas-cache-de-build-e-boas-praticas-3n80</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: do Dockerfile mínimo a um Dockerfile de verdade
&lt;/h2&gt;

&lt;p&gt;Na segunda parte desta série, um &lt;code&gt;Dockerfile&lt;/code&gt; de poucas linhas já foi suficiente para empacotar uma aplicação Python. Isso funciona, mas um Dockerfile escrito sem pensar em camadas e cache de build gera imagens maiores do que precisam ser e builds que demoram muito mais do que deveriam a cada mudança pequena no código. Este artigo aprofunda como o Docker constrói uma imagem por dentro, e como escrever um Dockerfile que tira proveito disso.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Como funcionam as camadas (layers)
&lt;/h2&gt;

&lt;p&gt;Cada instrução de um &lt;code&gt;Dockerfile&lt;/code&gt; (&lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;RUN&lt;/code&gt;, &lt;code&gt;COPY&lt;/code&gt;, &lt;code&gt;ADD&lt;/code&gt;) que modifica o sistema de arquivos gera uma &lt;strong&gt;camada&lt;/strong&gt; — um diff read-only armazenado separadamente e empilhado sobre as anteriores. A imagem final é simplesmente a soma de todas essas camadas, e o container em execução adiciona uma camada gravável no topo (union filesystem).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Container (camada gravável)
──────────────────────────
Camada 4: COPY . .
Camada 3: RUN pip install -r requirements.txt
Camada 2: COPY requirements.txt .
Camada 1: FROM python:3.12-slim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Duas consequências práticas importantes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Camadas são reaproveitadas entre imagens.&lt;/strong&gt; Se duas imagens diferentes compartilham as mesmas primeiras instruções (por exemplo, a mesma &lt;code&gt;FROM&lt;/code&gt; e o mesmo &lt;code&gt;RUN apt-get install&lt;/code&gt;), o Docker armazena essa camada uma única vez em disco, mesmo que várias imagens a usem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camadas são cacheadas entre builds.&lt;/strong&gt; Ao rodar &lt;code&gt;docker build&lt;/code&gt; de novo, o Docker verifica cada instrução, na ordem: se a instrução e seus arquivos de entrada não mudaram desde o último build, ele reaproveita a camada já construída em vez de refazer o trabalho. Isso é a base de todo o próximo tópico.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Cache de build: ordenar o Dockerfile por frequência de mudança
&lt;/h2&gt;

&lt;p&gt;O cache de build é &lt;strong&gt;invalidado a partir do primeiro ponto de mudança&lt;/strong&gt;: se a instrução N mudou (ou um arquivo que ela copia mudou), toda camada a partir de N é reconstruída — mesmo que as instruções seguintes sejam idênticas ao build anterior. Isso significa que a ordem das instruções no Dockerfile importa tanto quanto o conteúdo delas.&lt;/p&gt;

&lt;p&gt;O erro mais comum é copiar todo o código-fonte antes de instalar dependências:&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="c"&gt;# Ruim: qualquer mudança no código invalida o cache do pip install&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python3", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como &lt;code&gt;COPY . .&lt;/code&gt; copia o código inteiro, qualquer alteração em qualquer arquivo — inclusive um &lt;code&gt;README.md&lt;/code&gt; — muda o conteúdo dessa camada e invalida tudo que vem depois, incluindo o &lt;code&gt;pip install&lt;/code&gt;, que normalmente é o passo mais lento do build.&lt;/p&gt;

&lt;p&gt;A correção é separar o que muda com frequência (código) do que muda pouco (lista de dependências), copiando e instalando dependências primeiro:&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="c"&gt;# Bom: pip install só roda de novo se requirements.txt mudar&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python3", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora, alterar apenas o código da aplicação (o caso mais comum durante o desenvolvimento) reaproveita a camada do &lt;code&gt;pip install&lt;/code&gt; do cache, e o build inteiro cai de dezenas de segundos (ou minutos, em projetos com muitas dependências) para menos de um segundo nessa etapa.&lt;/p&gt;

&lt;p&gt;O mesmo princípio vale para qualquer gerenciador de pacotes — Node.js (&lt;code&gt;package.json&lt;/code&gt; antes do código), Go (&lt;code&gt;go.mod&lt;/code&gt;/&lt;code&gt;go.sum&lt;/code&gt; antes do código), Ruby (&lt;code&gt;Gemfile&lt;/code&gt; antes do código): copiar o manifesto de dependências, instalar, só então copiar o resto.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Minimizando o número e o tamanho das camadas
&lt;/h2&gt;

&lt;p&gt;Cada &lt;code&gt;RUN&lt;/code&gt; gera uma nova camada, e camadas nunca "encolhem" — se um &lt;code&gt;RUN&lt;/code&gt; baixa um arquivo temporário e um &lt;code&gt;RUN&lt;/code&gt; seguinte o apaga, o espaço ocupado por ele na camada anterior continua contando no tamanho final da imagem. Por isso, passos relacionados costumam ser combinados em um único &lt;code&gt;RUN&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="c"&gt;# Ruim: cache do apt fica preso numa camada, mesmo "removido" depois&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl git
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Bom: um único RUN, uma única camada, sem sobra&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; curl git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--no-install-recommends&lt;/code&gt; evita instalar pacotes "sugeridos" que o &lt;code&gt;apt-get&lt;/code&gt; traria por padrão, mas que raramente são necessários dentro de um container — outra fonte comum de peso desnecessário na imagem.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Escolhendo a imagem base certa
&lt;/h2&gt;

&lt;p&gt;A imagem base declarada em &lt;code&gt;FROM&lt;/code&gt; é o maior fator isolado no tamanho final. Para a mesma linguagem, variantes existem com trade-offs diferentes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variante&lt;/th&gt;
&lt;th&gt;Tamanho aproximado&lt;/th&gt;
&lt;th&gt;Quando usar&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python:3.12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~1 GB&lt;/td&gt;
&lt;td&gt;Precisa de ferramentas de build completas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python:3.12-slim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~150 MB&lt;/td&gt;
&lt;td&gt;Maioria dos casos — Debian mínimo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python:3.12-alpine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~50 MB&lt;/td&gt;
&lt;td&gt;Tamanho é crítico; cuidado com compatibilidade&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;alpine&lt;/code&gt; usa &lt;code&gt;musl&lt;/code&gt; em vez de &lt;code&gt;glibc&lt;/code&gt;, o que ocasionalmente quebra dependências Python com extensões compiladas em C (como &lt;code&gt;numpy&lt;/code&gt; ou &lt;code&gt;psycopg2&lt;/code&gt;) — vale testar antes de adotar em produção, em vez de assumir que é sempre a melhor escolha só por ser a menor imagem.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. &lt;code&gt;.dockerignore&lt;/code&gt;: cortando o que não deveria nem chegar ao build
&lt;/h2&gt;

&lt;p&gt;Antes de qualquer instrução rodar, o Docker envia todo o &lt;strong&gt;contexto de build&lt;/strong&gt; (o diretório passado como argumento final de &lt;code&gt;docker build&lt;/code&gt;, por exemplo &lt;code&gt;.&lt;/code&gt;) para o daemon. Sem um &lt;code&gt;.dockerignore&lt;/code&gt;, isso costuma incluir lixo irrelevante — e às vezes sensível:&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="c"&gt;# .dockerignore&lt;/span&gt;
.git
node_modules
__pycache__
*.pyc
.env
.venv
*.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Além de acelerar o envio do contexto (relevante em projetos com dependências pesadas versionadas localmente, como &lt;code&gt;node_modules&lt;/code&gt;), isso evita vazar segredos: um &lt;code&gt;.env&lt;/code&gt; com credenciais copiado por acidente via &lt;code&gt;COPY . .&lt;/code&gt; fica embutido permanentemente em uma camada da imagem, mesmo que um passo posterior o apague.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Entender camadas e cache de build muda a forma de escrever Dockerfiles: ordenar instruções da menos volátil para a mais volátil, combinar &lt;code&gt;RUN&lt;/code&gt;s relacionados, escolher a imagem base certa para o caso de uso e manter um &lt;code&gt;.dockerignore&lt;/code&gt; atualizado são hábitos que se pagam em builds mais rápidos e imagens menores desde o primeiro dia. No próximo artigo, o assunto muda de como as imagens são construídas para como os containers &lt;strong&gt;se comunicam&lt;/strong&gt;: redes, volumes e persistência de dados.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Docker — &lt;a href="https://commons.wikimedia.org/wiki/File:Docker_Logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;, fonte: docker.com/company/newsroom/media-resources&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/build/building/best-practices/" rel="noopener noreferrer"&gt;Docker Documentation — Dockerfile Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/build/cache/" rel="noopener noreferrer"&gt;Docker Documentation — Build Cache&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Docker no dia a dia - comandos essenciais e primeiros containers reais</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Thu, 13 Aug 2026 00:23:30 +0000</pubDate>
      <link>https://dev.to/apsis-cc/docker-no-dia-a-dia-comandos-essenciais-e-primeiros-containers-reais-5cd9</link>
      <guid>https://dev.to/apsis-cc/docker-no-dia-a-dia-comandos-essenciais-e-primeiros-containers-reais-5cd9</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: de imagens a containers em execução
&lt;/h2&gt;

&lt;p&gt;Na primeira parte desta série vimos o que é o Docker, o problema que ele resolve e os três conceitos fundamentais — imagens, containers e registries. Agora que a base teórica está posta, o foco deste artigo é prático: os comandos que efetivamente viram hábito no uso diário — &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt; — aplicados a containers reais, não só ao &lt;code&gt;hello-world&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;docker run&lt;/code&gt; além do básico
&lt;/h2&gt;

&lt;p&gt;O artigo anterior já usou &lt;code&gt;docker run&lt;/code&gt; para subir um Nginx. Vale conhecer as flags que aparecem o tempo todo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Modo interativo, útil para explorar uma imagem manualmente&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; ubuntu bash

&lt;span class="c"&gt;# Variáveis de ambiente&lt;/span&gt;
docker run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;segredo &lt;span class="nt"&gt;-d&lt;/span&gt; postgres

&lt;span class="c"&gt;# Montar um diretório do host dentro do container (volume bind mount)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/dados:/dados &lt;span class="nt"&gt;-d&lt;/span&gt; minha-imagem

&lt;span class="c"&gt;# Remover o container automaticamente quando ele parar&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; python:3.12 python3

&lt;span class="c"&gt;# Limitar recursos&lt;/span&gt;
docker run &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 minha-imagem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-it&lt;/code&gt; combina &lt;code&gt;-i&lt;/code&gt; (interativo, mantém STDIN aberto) com &lt;code&gt;-t&lt;/code&gt; (aloca um pseudo-terminal) — é o par de flags para "entrar" em um container e usar um shell como se fosse uma máquina normal.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--rm&lt;/code&gt; evita acumular containers parados no disco depois de testes rápidos e descartáveis — sem ela, cada &lt;code&gt;docker run&lt;/code&gt; deixa um container parado para trás até ser removido manualmente.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-e&lt;/code&gt; define variáveis de ambiente; imagens oficiais como a do Postgres costumam documentar quais variáveis elas esperam (usuário, senha, nome do banco inicial).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Inspecionando o que está rodando
&lt;/h2&gt;

&lt;p&gt;O comando mais usado para ter uma visão geral do que o Docker está gerenciando na máquina:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps              &lt;span class="c"&gt;# containers em execução&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;            &lt;span class="c"&gt;# todos, incluindo parados&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-q&lt;/span&gt;            &lt;span class="c"&gt;# só os ids (útil em scripts)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para investigar um container específico mais a fundo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker inspect meu-container      &lt;span class="c"&gt;# todos os metadados em JSON: rede, volumes, config&lt;/span&gt;
docker top meu-container          &lt;span class="c"&gt;# processos rodando dentro do container&lt;/span&gt;
docker stats                      &lt;span class="c"&gt;# uso de CPU/memória em tempo real de todos os containers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker stats&lt;/code&gt; é particularmente útil para detectar um container consumindo memória ou CPU muito acima do esperado, sem precisar entrar nele.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Logs: a primeira ferramenta de debug
&lt;/h2&gt;

&lt;p&gt;Containers geralmente não têm um arquivo de log tradicional acessível de fora — a convenção do Docker é que a aplicação escreva na saída padrão (stdout/stderr), e o Docker captura isso automaticamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs meu-container          &lt;span class="c"&gt;# todo o log acumulado&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; meu-container        &lt;span class="c"&gt;# segue o log em tempo real (como tail -f)&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 100 meu-container  &lt;span class="c"&gt;# só as últimas 100 linhas&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; 10m meu-container &lt;span class="c"&gt;# só os últimos 10 minutos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Na prática, &lt;code&gt;docker logs -f nome-do-container&lt;/code&gt; costuma ser o primeiro comando rodado ao investigar um container que não está se comportando como esperado — antes até de entrar nele.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;docker exec&lt;/code&gt;: entrando em um container já rodando
&lt;/h2&gt;

&lt;p&gt;Diferente de &lt;code&gt;docker run&lt;/code&gt; (que cria um &lt;strong&gt;novo&lt;/strong&gt; container), &lt;code&gt;docker exec&lt;/code&gt; executa um comando dentro de um container &lt;strong&gt;que já está rodando&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Abrir um shell dentro de um container em execução&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; meu-container bash

&lt;span class="c"&gt;# Rodar um comando pontual sem abrir shell interativo&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;meu-container &lt;span class="nb"&gt;ls&lt;/span&gt; /app

&lt;span class="c"&gt;# Útil para inspecionar o banco de um container Postgres&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; meu-postgres psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse é o comando do dia a dia para depurar um container em produção (ou em homologação) sem reiniciá-lo: verificar se um arquivo de configuração chegou certo, olhar o conteúdo de um diretório, testar conectividade de rede com &lt;code&gt;curl&lt;/code&gt; de dentro do container, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. &lt;code&gt;docker build&lt;/code&gt;: da aplicação à imagem
&lt;/h2&gt;

&lt;p&gt;Todos os exemplos até aqui usaram imagens prontas do Docker Hub. Para empacotar uma aplicação própria, o ponto de partida é um &lt;code&gt;Dockerfile&lt;/code&gt; (o Artigo 3 desta série é inteiramente dedicado a escrevê-los bem) — por agora, um exemplo mínimo para uma aplicação Python:&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="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python3", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Construir a imagem a partir desse arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; minha-app:1.0 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-t minha-app:1.0&lt;/code&gt; — dá um nome (&lt;code&gt;minha-app&lt;/code&gt;) e uma tag (&lt;code&gt;1.0&lt;/code&gt;) à imagem resultante. Sem tag explícita, o Docker usa &lt;code&gt;latest&lt;/code&gt; por padrão — algo a evitar em ambientes reais, porque &lt;code&gt;latest&lt;/code&gt; não diz nada sobre qual versão do código está de fato empacotada ali.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.&lt;/code&gt; — o &lt;strong&gt;contexto de build&lt;/strong&gt;: o diretório cujo conteúdo fica disponível para os comandos &lt;code&gt;COPY&lt;/code&gt;/&lt;code&gt;ADD&lt;/code&gt; do Dockerfile. Tudo dentro dele é enviado ao Docker daemon antes do build começar, então diretórios grandes e irrelevantes (como &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;) devem ser excluídos com um arquivo &lt;code&gt;.dockerignore&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depois de construída, a imagem já pode ser rodada como qualquer outra:&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 &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; minha-app-container minha-app:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Limpando o ambiente
&lt;/h2&gt;

&lt;p&gt;Containers parados, imagens não usadas e volumes órfãos se acumulam rápido durante o desenvolvimento. Comandos úteis de limpeza:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker container prune    &lt;span class="c"&gt;# remove todos os containers parados&lt;/span&gt;
docker image prune         &lt;span class="c"&gt;# remove imagens "dangling" (sem tag, órfãs de build)&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt;      &lt;span class="c"&gt;# remove também imagens não usadas por nenhum container&lt;/span&gt;
docker system prune        &lt;span class="c"&gt;# limpeza geral: containers parados, redes e imagens não usadas&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vale rodar &lt;code&gt;docker system df&lt;/code&gt; antes, para ter uma ideia de quanto espaço em disco está sendo ocupado por cada categoria (imagens, containers, volumes, cache de build) antes de decidir o que limpar.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Com &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt; e &lt;code&gt;build&lt;/code&gt;, já é possível cobrir o ciclo completo do dia a dia: subir containers, inspecionar o que está rodando, depurar problemas e empacotar uma aplicação própria em imagem. No próximo artigo, o foco entra a fundo no &lt;code&gt;Dockerfile&lt;/code&gt;: como as camadas funcionam, como aproveitar o cache de build para acelerar builds repetidos, e boas práticas para escrever um Dockerfile enxuto e eficiente.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Docker — &lt;a href="https://commons.wikimedia.org/wiki/File:Docker_Logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;, fonte: docker.com/company/newsroom/media-resources&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/reference/cli/docker/" rel="noopener noreferrer"&gt;Docker CLI Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/reference/dockerfile/" rel="noopener noreferrer"&gt;Docker Documentation — Dockerfile Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Docker - O Que É, Para Que Serve e Conceitos Iniciais</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Wed, 12 Aug 2026 09:30:09 +0000</pubDate>
      <link>https://dev.to/apsis-cc/docker-o-que-e-para-que-serve-e-conceitos-iniciais-18g6</link>
      <guid>https://dev.to/apsis-cc/docker-o-que-e-para-que-serve-e-conceitos-iniciais-18g6</guid>
      <description>&lt;h2&gt;
  
  
  1. O Problema que o Docker Resolve
&lt;/h2&gt;

&lt;p&gt;"Na minha máquina funciona." Poucas frases resumem tão bem um problema que atormentou (e ainda atormenta) times de desenvolvimento: um código que roda perfeitamente no notebook do desenvolvedor, mas quebra no servidor de produção — porque a versão do Python é outra, uma biblioteca do sistema está faltando, uma variável de ambiente não foi configurada, ou o sistema operacional simplesmente se comporta de forma diferente.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;Docker&lt;/strong&gt; resolve exatamente isso: ele empacota uma aplicação junto com tudo que ela precisa para rodar — código, dependências, bibliotecas do sistema, variáveis de ambiente, configuração — em uma unidade isolada e portátil chamada &lt;strong&gt;container&lt;/strong&gt;. Essa unidade roda da mesma forma em qualquer lugar que tenha o Docker instalado: no notebook do desenvolvedor, no servidor de CI, ou em produção. Esta é a primeira parte de uma série que vai do zero ao avançado em Docker: hoje o foco é entender o problema que ele resolve, os conceitos fundamentais e como eles se encaixam.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Containers vs Máquinas Virtuais
&lt;/h2&gt;

&lt;p&gt;A comparação mais comum ao explicar Docker é com máquinas virtuais (VMs), porque ambos resolvem um problema parecido — isolar e empacotar aplicações — mas de formas muito diferentes.&lt;/p&gt;

&lt;p&gt;Uma &lt;strong&gt;máquina virtual&lt;/strong&gt; virtualiza o hardware inteiro: cada VM roda seu próprio sistema operacional completo (kernel incluso), gerenciado por um hypervisor. Isso garante isolamento forte, mas tem um custo alto: cada VM consome centenas de MBs a alguns GBs de disco e memória só para o SO, e leva de dezenas de segundos a minutos para inicializar.&lt;/p&gt;

&lt;p&gt;Um &lt;strong&gt;container&lt;/strong&gt;, por outro lado, virtualiza no nível do sistema operacional: todos os containers em uma máquina compartilham o mesmo kernel do host, mas cada um enxerga seu próprio sistema de arquivos, processos e rede isolados — usando recursos do kernel Linux como &lt;code&gt;namespaces&lt;/code&gt; (isolamento de visão) e &lt;code&gt;cgroups&lt;/code&gt; (limites de CPU/memória). O resultado é que containers são muito mais leves: alguns MBs a poucas centenas de MBs, com inicialização em milissegundos a poucos segundos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────┐        ┌─────────────────────────┐
│   VM 1    │    VM 2      │        │  Container 1│Container 2│
│  ┌─────┐  │   ┌─────┐    │        │  ┌───────┐  │ ┌───────┐ │
│  │ App │  │   │ App │    │        │  │  App  │  │ │  App  │ │
│  ├─────┤  │   ├─────┤    │        │  ├───────┤  │ ├───────┤ │
│  │ SO   │  │   │ SO  │    │        │  │ Libs  │  │ │ Libs  │ │
│  └─────┘  │   └─────┘    │        │  └───────┘  │ └───────┘ │
├───────────┴──────────────┤        ├─────────────┴───────────┤
│        Hypervisor        │        │      Docker Engine       │
├───────────────────────────┤        ├───────────────────────────┤
│     SO Host + Hardware    │        │     SO Host + Hardware    │
└───────────────────────────┘        └───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso não significa que containers substituem VMs em todo cenário — VMs continuam sendo a escolha certa quando o isolamento precisa ser total (por exemplo, rodar cargas de múltiplos clientes não confiáveis na mesma máquina física) ou quando se precisa de um kernel diferente do host. Mas para o caso mais comum — empacotar e distribuir aplicações de forma consistente — containers ganham em leveza, velocidade de inicialização e densidade (quantas cargas cabem na mesma máquina).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Os Três Conceitos Fundamentais: Imagens, Containers e Registries
&lt;/h2&gt;

&lt;p&gt;Todo o modelo mental do Docker gira em torno de três peças:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Imagem (image):&lt;/strong&gt; um pacote read-only com tudo que uma aplicação precisa para rodar — sistema de arquivos, binários, bibliotecas, código da aplicação e metadados (como qual comando executar ao iniciar). É construída a partir de um &lt;code&gt;Dockerfile&lt;/code&gt; (assunto do Artigo 3 desta série) e organizada em camadas (layers) empilhadas, o que permite reaproveitar partes já construídas entre builds diferentes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container:&lt;/strong&gt; uma &lt;strong&gt;instância em execução&lt;/strong&gt; de uma imagem. Se a imagem é a "planta" (como uma classe em programação orientada a objetos), o container é o "objeto" instanciado a partir dela — com um processo rodando, um sistema de arquivos gravável em cima da imagem read-only, e seu próprio espaço de rede isolado. É possível rodar múltiplos containers a partir da mesma imagem, cada um independente dos outros.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registry:&lt;/strong&gt; um repositório para armazenar e distribuir imagens. O &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt; é o registry público padrão (onde vivem imagens oficiais como &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;postgres&lt;/code&gt;, &lt;code&gt;nginx&lt;/code&gt;), mas existem registries privados (AWS ECR, Google Artifact Registry, GitHub Container Registry) para imagens internas de uma empresa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O fluxo típico conecta essas três peças: escreve-se um &lt;code&gt;Dockerfile&lt;/code&gt;, constrói-se uma &lt;strong&gt;imagem&lt;/strong&gt; a partir dele, essa imagem é enviada (&lt;code&gt;push&lt;/code&gt;) para um &lt;strong&gt;registry&lt;/strong&gt;, e em qualquer máquina com Docker instalado é possível baixá-la (&lt;code&gt;pull&lt;/code&gt;) e rodar um ou mais &lt;strong&gt;containers&lt;/strong&gt; a partir dela.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dockerfile → (build) → Imagem → (push) → Registry
                                              │
                                          (pull)
                                              │
                                              ▼
                                        Imagem local → (run) → Container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Instalando o Docker
&lt;/h2&gt;

&lt;p&gt;O Docker está disponível para Linux, macOS e Windows. Em distribuições Linux, o pacote oficial (não o &lt;code&gt;docker.io&lt;/code&gt; genérico de alguns repositórios, que costuma ficar desatualizado) é instalado assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Debian/Ubuntu — script oficial de conveniência&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.docker.com | sh

&lt;span class="c"&gt;# Depois, para rodar docker sem sudo (requer novo login/logout):&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em macOS e Windows, a forma mais comum é instalar o &lt;strong&gt;Docker Desktop&lt;/strong&gt;, que empacota o engine, uma VM Linux leve (necessária porque o Docker depende de recursos do kernel Linux) e uma interface gráfica.&lt;/p&gt;

&lt;p&gt;Depois de instalado, confirme que está funcionando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O segundo comando baixa uma imagem mínima do Docker Hub, roda um container a partir dela (que imprime uma mensagem de confirmação e termina) — é o "hello world" oficial do ecossistema Docker, útil para validar que o engine está rodando e tem permissão para baixar imagens.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Um Primeiro Container na Prática
&lt;/h2&gt;

&lt;p&gt;Para sair da teoria, um exemplo real: rodar um servidor web Nginx sem instalar nada além do Docker na máquina.&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 &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; meu-nginx nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decompondo o comando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker run&lt;/code&gt; — cria e inicia um container a partir de uma imagem.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d&lt;/code&gt; (detached) — roda o container em segundo plano, devolvendo o terminal imediatamente.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p 8080:80&lt;/code&gt; — mapeia a porta 8080 da máquina host para a porta 80 dentro do container (onde o Nginx escuta por padrão).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--name meu-nginx&lt;/code&gt; — dá um nome fácil de referenciar ao container, em vez de um id gerado automaticamente.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nginx&lt;/code&gt; — a imagem a usar. Como não existe localmente ainda, o Docker automaticamente faz &lt;code&gt;pull&lt;/code&gt; dela do Docker Hub antes de rodar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Acessar &lt;code&gt;http://localhost:8080&lt;/code&gt; no navegador já mostra a página padrão do Nginx. Para conferir que o container está rodando e depois removê-lo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps                    &lt;span class="c"&gt;# lista containers em execução&lt;/span&gt;
docker stop meu-nginx        &lt;span class="c"&gt;# para o container&lt;/span&gt;
docker &lt;span class="nb"&gt;rm &lt;/span&gt;meu-nginx          &lt;span class="c"&gt;# remove o container (já parado)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note que &lt;code&gt;docker stop&lt;/code&gt; e &lt;code&gt;docker rm&lt;/code&gt; são passos separados — parar um container não o remove, apenas encerra o processo dentro dele. Isso é proposital: permite inspecionar o estado final de um container que falhou antes de descartá-lo. Os comandos do dia a dia como esses (&lt;code&gt;run&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;) são o assunto completo do próximo artigo.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Conclusão e Próximos Passos
&lt;/h2&gt;

&lt;p&gt;Nesta primeira parte, vimos o problema real que o Docker resolve ("funciona na minha máquina"), como containers se diferenciam de máquinas virtuais em leveza e velocidade, os três conceitos que sustentam todo o ecossistema — imagens, containers e registries — e rodamos o primeiro container de ponta a ponta. No próximo artigo, o foco vai para os comandos que realmente viram hábito no dia a dia: &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt; e &lt;code&gt;build&lt;/code&gt;, com exemplos de containers reais além do "hello world".&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Docker — &lt;a href="https://commons.wikimedia.org/wiki/File:Docker_Logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;, fonte: docker.com/company/newsroom/media-resources&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-started/" rel="noopener noreferrer"&gt;Docker Documentation — Get Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-started/docker-overview/" rel="noopener noreferrer"&gt;Docker Documentation — Docker Overview&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Tmux Avançado - Exemplos Reais e Configurações para Produtividade</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Wed, 05 Aug 2026 09:30:51 +0000</pubDate>
      <link>https://dev.to/apsis-cc/tmux-avancado-exemplos-reais-e-configuracoes-para-produtividade-2c9c</link>
      <guid>https://dev.to/apsis-cc/tmux-avancado-exemplos-reais-e-configuracoes-para-produtividade-2c9c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7sxiloeoqe8mphqsr28.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7sxiloeoqe8mphqsr28.gif" alt="Demo do tmuxinator criando uma sessão com múltiplas janelas via script" width="599" height="337"&gt;&lt;/a&gt; &lt;em&gt;tmuxinator em ação — &lt;a href="https://github.com/tmuxinator/tmuxinator" rel="noopener noreferrer"&gt;tmuxinator/tmuxinator&lt;/a&gt;, licença MIT&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Fechando a Série
&lt;/h2&gt;

&lt;p&gt;Nas três primeiras partes desta série, cobrimos o que é o tmux, os comandos de navegação entre sessões/janelas/painéis e a personalização via &lt;code&gt;~/.tmux.conf&lt;/code&gt; e plugins. Este último artigo é sobre uso avançado: como automatizar a criação de layouts complexos, integrar o tmux com fluxos de trabalho remotos, sincronizar comandos entre múltiplos servidores e ajustar detalhes de configuração que fazem diferença no uso intenso do dia a dia.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Automatizando Sessões com Scripts
&lt;/h2&gt;

&lt;p&gt;Montar manualmente o mesmo layout (editor, servidor, logs) toda vez que um projeto é retomado é repetitivo. A própria CLI do tmux permite montar uma sessão inteira via script, sem interação manual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# start-project.sh — monta a sessão de trabalho do projeto "blog"&lt;/span&gt;

&lt;span class="nv"&gt;SESSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"blog"&lt;/span&gt;

tmux new-session &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; ~/projects/blog

&lt;span class="c"&gt;# Janela 0: editor&lt;/span&gt;
tmux rename-window &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;:0"&lt;/span&gt; &lt;span class="s2"&gt;"editor"&lt;/span&gt;
tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;:editor"&lt;/span&gt; &lt;span class="s2"&gt;"nvim ."&lt;/span&gt; C-m

&lt;span class="c"&gt;# Janela 1: servidor + logs, dividida em dois painéis&lt;/span&gt;
tmux new-window &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"server"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; ~/projects/blog
tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;:server"&lt;/span&gt; &lt;span class="s2"&gt;"npm run dev"&lt;/span&gt; C-m
tmux split-window &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;:server"&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; ~/projects/blog
tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;:server"&lt;/span&gt; &lt;span class="s2"&gt;"tail -f logs/app.log"&lt;/span&gt; C-m

&lt;span class="c"&gt;# Janela 2: terminal solto, sem comando automático&lt;/span&gt;
tmux new-window &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; ~/projects/blog

tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-window&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;:editor"&lt;/span&gt;
tmux attach &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rodar &lt;code&gt;./start-project.sh&lt;/code&gt; recria o layout inteiro em segundos. Para projetos com estrutura parecida, ferramentas como o &lt;a href="https://github.com/tmuxinator/tmuxinator" rel="noopener noreferrer"&gt;tmuxinator&lt;/a&gt; ou o &lt;a href="https://github.com/tmux-python/tmuxp" rel="noopener noreferrer"&gt;tmuxp&lt;/a&gt; fazem o mesmo a partir de um arquivo YAML declarativo, o que fica mais legível quando há muitas janelas e painéis envolvidos:&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="c1"&gt;# .tmuxp.yaml&lt;/span&gt;
&lt;span class="na"&gt;session_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog&lt;/span&gt;
&lt;span class="na"&gt;start_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~/projects/blog&lt;/span&gt;
&lt;span class="na"&gt;windows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;window_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;editor&lt;/span&gt;
    &lt;span class="na"&gt;panes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nvim .&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;window_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server&lt;/span&gt;
    &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;even-vertical&lt;/span&gt;
    &lt;span class="na"&gt;panes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run dev&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;tail -f logs/app.log&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;window_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shell&lt;/span&gt;
    &lt;span class="na"&gt;panes&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;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmuxp load .tmuxp.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Persistência de Sessões em Servidores Remotos
&lt;/h2&gt;

&lt;p&gt;O caso de uso mais comum em produção é abrir uma sessão nomeada logo após conectar via SSH, sempre reaproveitando a mesma se já existir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh usuario@servidor &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s1"&gt;'tmux new-session -A -s trabalho'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A flag &lt;code&gt;-A&lt;/code&gt; faz o tmux &lt;strong&gt;anexar à sessão &lt;code&gt;trabalho&lt;/code&gt; se ela já existir, ou criá-la se ainda não existir&lt;/strong&gt; — elimina a necessidade de lembrar se já havia uma sessão aberta antes. Combinado com &lt;code&gt;tmux-resurrect&lt;/code&gt; e &lt;code&gt;tmux-continuum&lt;/code&gt; (vistos na parte anterior), até uma reinicialização do servidor não é motivo para perder o layout: basta reconectar e rodar &lt;code&gt;prefix + Ctrl+r&lt;/code&gt; para restaurar.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Sincronizando Painéis Entre Múltiplos Hosts
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzj4qh8whxmkl4kly0i2i.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzj4qh8whxmkl4kly0i2i.png" alt="Miniatura do screencast do tmux-resurrect, mostrando o estado de uma sessão sendo restaurado" width="400" height="225"&gt;&lt;/a&gt; &lt;em&gt;tmux-resurrect — &lt;a href="https://vimeo.com/104763018" rel="noopener noreferrer"&gt;screencast completo no Vimeo&lt;/a&gt;, &lt;a href="https://github.com/tmux-plugins/tmux-resurrect" rel="noopener noreferrer"&gt;tmux-plugins/tmux-resurrect&lt;/a&gt;, licença MIT&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Quando o mesmo comando precisa rodar em vários servidores ao mesmo tempo (por exemplo, checar a versão de um pacote em todos os nós de um cluster), o tmux permite espelhar a digitação em todos os painéis abertos de uma janela:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Uma janela com um painel por servidor, cada um já conectado via SSH&lt;/span&gt;
tmux new-window &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"cluster"&lt;/span&gt;
tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"cluster.0"&lt;/span&gt; &lt;span class="s2"&gt;"ssh node1"&lt;/span&gt; C-m
tmux split-window &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"cluster"&lt;/span&gt;
tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"cluster.1"&lt;/span&gt; &lt;span class="s2"&gt;"ssh node2"&lt;/span&gt; C-m
tmux split-window &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"cluster"&lt;/span&gt;
tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"cluster.2"&lt;/span&gt; &lt;span class="s2"&gt;"ssh node3"&lt;/span&gt; C-m
tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-layout&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"cluster"&lt;/span&gt; tiled

&lt;span class="c"&gt;# Ativa a sincronização: tudo que for digitado vai para todos os painéis&lt;/span&gt;
tmux setw synchronize-panes on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com &lt;code&gt;synchronize-panes on&lt;/code&gt;, qualquer comando digitado é replicado em tempo real em todos os painéis da janela — útil para operações repetitivas em múltiplos hosts, mas vale lembrar de desativar (&lt;code&gt;synchronize-panes off&lt;/code&gt;) antes de rodar algo destrutivo em apenas um deles por engano.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Tmux Dentro de Tmux (Sessões Aninhadas)
&lt;/h2&gt;

&lt;p&gt;Ao conectar via SSH a um servidor remoto que também roda tmux, de dentro de uma sessão tmux local, o prefixo do teclado passa a ser interpretado pela sessão mais externa (a local), não pela remota. A solução mais comum é alternar temporariamente o destino do prefixo com &lt;code&gt;Ctrl+x&lt;/code&gt; &lt;code&gt;Ctrl+x&lt;/code&gt; (pressionar o prefixo duas vezes envia o segundo diretamente para a sessão interna), ou configurar um prefixo alternativo específico para uso remoto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# No .tmux.conf da sessão externa (local), o segundo Ctrl+x repassa para a interna
bind-key -n C-x send-prefix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma alternativa mais simples no dia a dia é usar &lt;code&gt;Ctrl+b&lt;/code&gt; &lt;code&gt;d&lt;/code&gt; para desanexar a sessão remota antes de mexer na local, evitando a ambiguidade por completo.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Ajustes Finos de Configuração
&lt;/h2&gt;

&lt;p&gt;Alguns parâmetros que fazem diferença perceptível no uso intenso, para adicionar ao &lt;code&gt;~/.tmux.conf&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;# Reduz o delay entre pressionar Esc e o terminal reconhecer (importante para vim/neovim)
set -sg escape-time 0

# Histórico de scroll maior (padrão é só 2000 linhas)
set -g history-limit 50000

# Renumera as janelas automaticamente ao fechar uma no meio
set -g renumber-windows on

# Evita que o tmux renomeie janelas automaticamente com base no comando em execução
set-option -g allow-rename off

# Notificação visual (não sonora) quando outra janela tem atividade
setw -g monitor-activity on
set -g visual-activity on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;escape-time 0&lt;/code&gt; em particular resolve um sintoma clássico de quem usa Vim/Neovim dentro do tmux: um pequeno atraso perceptível ao sair do modo de inserção com &lt;code&gt;Esc&lt;/code&gt;, causado pelo tmux esperando para diferenciar a tecla Esc isolada de sequências de escape de teclas especiais.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão da Série
&lt;/h2&gt;

&lt;p&gt;Ao longo destes quatro artigos, o tmux saiu de "o que é isso e por que alguém usaria" até scripts de automação de sessão, persistência em servidores remotos e sincronização entre múltiplos hosts. A curva de aprendizado inicial (decorar o prefixo, os atalhos de painel) se paga rápido assim que uma conexão cai no meio de um trabalho importante e tudo continua exatamente de pé ao reconectar. Vale começar pequeno — um &lt;code&gt;.tmux.conf&lt;/code&gt; com poucas linhas e um ou dois plugins — e ir incorporando o restante conforme o próprio fluxo de trabalho pedir.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo do tmux, por Jason Long — &lt;a href="https://commons.wikimedia.org/wiki/File:Tmux_logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/tmuxinator/tmuxinator" rel="noopener noreferrer"&gt;tmuxinator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmux-python/tmuxp" rel="noopener noreferrer"&gt;tmuxp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmux/tmux/wiki" rel="noopener noreferrer"&gt;tmux GitHub Wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>tmux</category>
      <category>linux</category>
      <category>cli</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Tmux Personalizado - Cores, Status Bar e Plugins Úteis</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Tue, 04 Aug 2026 09:30:57 +0000</pubDate>
      <link>https://dev.to/apsis-cc/tmux-personalizado-cores-status-bar-e-plugins-uteis-9e6</link>
      <guid>https://dev.to/apsis-cc/tmux-personalizado-cores-status-bar-e-plugins-uteis-9e6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjzgoe4zyn1mvo8j7ltnh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjzgoe4zyn1mvo8j7ltnh.webp" alt="Status bar do tmux com o tema Catppuccin Frappé, igual ao usado neste post" width="799" height="299"&gt;&lt;/a&gt; &lt;em&gt;Catppuccin Frappé — &lt;a href="https://github.com/catppuccin/tmux" rel="noopener noreferrer"&gt;catppuccin/tmux&lt;/a&gt;, licença MIT&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Onde Mora a Configuração do Tmux
&lt;/h2&gt;

&lt;p&gt;Nas duas primeiras partes desta série, vimos os conceitos do tmux e os comandos para navegar entre sessões, janelas e painéis. Tudo isso funciona com os padrões de fábrica — mas o tmux só realmente "gruda" no fluxo de trabalho quando é ajustado ao gosto de quem usa: prefixo mais confortável, atalhos no estilo vi, status bar informativa e plugins que resolvem problemas recorrentes. Em vez de montar um exemplo genérico, este artigo usa como base uma configuração real, em uso diário, com tema Catppuccin e integração com Docker e Git direto na status bar.&lt;/p&gt;

&lt;p&gt;Toda essa personalização mora em um único arquivo: &lt;code&gt;~/.tmux.conf&lt;/code&gt;. Ele é lido quando o servidor tmux inicia; para aplicar mudanças em uma sessão já em execução, sem reiniciar tudo, recarrega-se manualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux source-file ~/.tmux.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mapear isso para uma combinação de teclas dentro do próprio &lt;code&gt;.tmux.conf&lt;/code&gt; evita ter que digitar o comando inteiro toda vez que uma linha é ajustada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unbind r
bind r source-file ~/.tmux.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Prefixo, Atalhos e Popups
&lt;/h2&gt;

&lt;p&gt;O prefixo padrão &lt;code&gt;Ctrl+b&lt;/code&gt; é uma escolha histórica (evitar conflito com o &lt;code&gt;Ctrl+a&lt;/code&gt; do GNU Screen), mas boa parte da comunidade prefere algo mais perto do canto do teclado. Aqui a escolha caiu sobre &lt;code&gt;Ctrl+x&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;# Alterar a tecla Leader (Prefix) de Ctrl+b para Ctrl+x
set -g prefix C-x
unbind C-b
bind C-x send-prefix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Divisão de painéis mantendo o diretório atual, navegação estilo vi e redimensionamento com &lt;code&gt;shift&lt;/code&gt;+direção:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Split mantendo o diretório atual
bind '"' split-window -v -c "#{pane_current_path}"
bind '%' split-window -h -c "#{pane_current_path}"

# Navegação entre painéis (estilo vim)
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# Redimensionar painéis com shift+seta
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5

# Copy mode com vi keys
setw -g mode-keys vi
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;-r&lt;/code&gt; nos binds de resize permite repetir o atalho várias vezes seguidas sem apertar o prefixo de novo a cada aperto — útil quando um painel precisa crescer bem mais que 5 colunas/linhas de uma vez.&lt;/p&gt;

&lt;p&gt;Um recurso menos comum: &lt;code&gt;display-popup&lt;/code&gt;, que abre uma janela flutuante por cima da sessão sem criar um painel novo — ótimo para uma checagem rápida sem bagunçar o layout atual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bind D display-popup -w 80% -h 80% -E "docker stats"
bind P display-popup -w 80% -h 80% -b rounded -s "fg=black" -S "fg=cyan" -E "zsh"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-E&lt;/code&gt; roda o comando e fecha o popup assim que ele termina; &lt;code&gt;P&lt;/code&gt; abre um shell solto (&lt;code&gt;zsh&lt;/code&gt;) num popup com borda arredondada, útil para rodar algo pontual sem sair da janela atual nem abrir um painel permanente.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Cores e Status Bar com Catppuccin
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1kne8sc5t4et9d2wkdm9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1kne8sc5t4et9d2wkdm9.webp" alt="Status bar do tmux com o tema Catppuccin Mocha" width="799" height="299"&gt;&lt;/a&gt; &lt;em&gt;Catppuccin Mocha, outro flavor do mesmo tema — &lt;a href="https://github.com/catppuccin/tmux" rel="noopener noreferrer"&gt;catppuccin/tmux&lt;/a&gt;, licença MIT&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Escrever cada cor da status bar manualmente (como &lt;code&gt;status-style bg=colour235,fg=colour250&lt;/code&gt;) funciona, mas dá trabalho para manter consistente em todos os elementos — painéis, mensagens, janela ativa. O plugin &lt;a href="https://github.com/catppuccin/tmux" rel="noopener noreferrer"&gt;catppuccin/tmux&lt;/a&gt; resolve isso fornecendo uma paleta e um conjunto de módulos prontos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -g @catppuccin_flavor "frappe"
set -g @catppuccin_window_status_style "rounded"
set -g @catppuccin_window_text "#H"
set -g @catppuccin_window_current_text "#W"
set -g @catppuccin_session_text " #I:#S"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O flavor &lt;code&gt;frappe&lt;/code&gt; é uma das quatro variações de paleta do Catppuccin (as outras são &lt;code&gt;latte&lt;/code&gt;, &lt;code&gt;macchiato&lt;/code&gt; e &lt;code&gt;mocha&lt;/code&gt;) — trocar essa única linha muda o esquema de cores inteiro, sem tocar em mais nada. As cores de base do frappe aparecem depois no arquivo, para uso em outros elementos que o módulo do Catppuccin não cobre diretamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -g @ctp_bg "#303446"        # Base
set -g @ctp_surface_1 "#51576d" # Surface1
set -g @ctp_fg "#c6d0f5"        # Text
set -g @ctp_mauve "#ca9ee6"     # Mauve
set -g @ctp_crust "#232634"     # Crust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um detalhe que vale a pena copiar: detecção de sessão SSH via diretiva condicional do próprio tmux, deixando a status bar visivelmente diferente (laranja) sempre que a sessão está numa máquina remota — um lembrete visual de "cuidado, você não está local":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%if "#{SSH_CLIENT}"
set -g status-style "bg=#fe640b,fg=#ffffff"
set -g message-style "bg=#e64553,fg=#ffffff"
%else
set -g status-style "bg=#e78284,fg=#232634"
set -g message-style "bg=#ea999c,fg=#232634"
%endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;%if&lt;/code&gt;/&lt;code&gt;%else&lt;/code&gt;/&lt;code&gt;%endif&lt;/code&gt; são avaliados quando o tmux lê o arquivo, com base em uma variável de formato — aqui, &lt;code&gt;#{SSH_CLIENT}&lt;/code&gt; só é diferente de vazio quando a conexão veio via SSH.&lt;/p&gt;

&lt;p&gt;A composição da status bar é feita concatenando pedaços com &lt;code&gt;-a&lt;/code&gt; (append), em vez de escrever uma string gigante numa linha só:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set-option -g status-position top
set -g status-left-length 100
set -g status-right-length 100
set -g status-left ""
set -g window-status-format ""
set -g window-status-current-format ""

# Sessão + hostname
set -gF status-left "#[fg=#babbf1]   ##H "
set -ag status-left "#{E:@catppuccin_status_session}"

# Badge de SSH, aparece só quando conectado remotamente
set -ag status-left '#([ -n "$SSH_CLIENT" ] &amp;amp;&amp;amp; echo "#[fg=#ffffff bg=#fe640b bold] SSH #[default]") '

# Containers Docker rodando / parados, CPU e memória agregadas
set -ag status-left "#[fg=#a6d189,bold] #(docker ps -q 2&amp;gt;/dev/null | wc -l | tr -d ' ')#[fg=#626880]/#[fg=#e78284]#(docker ps -aq --filter status=exited 2&amp;gt;/dev/null | wc -l | tr -d ' ') "
set -ag status-left "#[fg=#ef9f76] #(docker stats --no-stream --format '{{.CPUPerc}}' 2&amp;gt;/dev/null | sed 's/[^0-9.]//g' | awk '{sum+=$1} END {print int(sum*10)/10}')%% "
set -ag status-left "#[fg=#85c1dc] #(docker stats --no-stream --format '{{.MemUsage}}' 2&amp;gt;/dev/null | awk -F'/' 'NR==1{print $1}' | tr -d ' ') "

# Aplicação atual, uptime e métricas do host (plugins, seção 5)
set -g status-right "#{E:@catppuccin_status_application}"
set -ag status-right "#{E:@catppuccin_status_uptime}"
set -agF status-right "#{E:@catppuccin_status_cpu}"
set -agF status-right "#{E:@catppuccin_status_ram}"
set -agF status-right "#{E:@catppuccin_status_load}"
set -agF status-right "#{E:@catppuccin_status_battery}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;#(comando)&lt;/code&gt; roda um shell command e injeta a saída direto na status bar — é assim que os containers Docker ativos e o uso de CPU/memória aparecem atualizados a cada &lt;code&gt;status-interval&lt;/code&gt; (60 segundos, por padrão, para não pesar rodando &lt;code&gt;docker stats&lt;/code&gt; toda hora):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -g status-interval 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por fim, os painéis também ganham borda colorida e um cabeçalho informativo, mostrando o comando rodando, a branch git do diretório atual (via &lt;code&gt;#(cd #{pane_current_path} &amp;amp;&amp;amp; git branch --show-current)&lt;/code&gt;) e o horário:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -g pane-border-lines single
set -g pane-border-status top
set -g pane-active-border-style "fg=#e5c890,bold"
set -g pane-border-style "fg=#006b6b"
set -g pane-border-format " #{?pane_active,#[fg=#a6d189 bold]▶ #[fg=#85c1dc bold],#[fg=#626880]}#{pane_current_command} #[align=right]#[fg=#ca9ee6]#(cd #{pane_current_path} &amp;amp;&amp;amp; git branch --show-current 2&amp;gt;/dev/null | sed '/^$/d; s/^/  /; s/$/ /') #[fg=#626880]· #[fg=#00ffff]%H:%M #[fg=#626880]· #[fg=#a6d189]#P "
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;#{?pane_active,X,Y}&lt;/code&gt; é um condicional de formato: mostra &lt;code&gt;X&lt;/code&gt; se o painel estiver ativo, &lt;code&gt;Y&lt;/code&gt; caso contrário — é assim que só o painel focado ganha a seta &lt;code&gt;▶&lt;/code&gt; destacada.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Tmux Plugin Manager (TPM)
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;TPM&lt;/strong&gt; (Tmux Plugin Manager) é o método padrão da comunidade para instalar e gerenciar plugins, de forma parecida com o &lt;code&gt;vim-plug&lt;/code&gt; no Vim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;~/.tmux.conf&lt;/code&gt;, plugins são declarados com &lt;code&gt;set -g @plugin&lt;/code&gt;, e a linha que inicializa o TPM &lt;strong&gt;precisa ficar sempre por último&lt;/strong&gt; no arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'jimeh/tmuxifier'
set -g @plugin 'tmux-plugins/tmux-battery'
set -g @plugin 'tmux-plugins/tmux-cpu'
set -g @plugin 'tmux-plugins/tmux-prefix-highlight'
set -g @plugin 'tmux-plugins/tmux-online-status'
set -g @plugin 'tmux-plugins/tmux-yank'
set -g @plugin 'tmux-plugins/tmux-continuum'

run -b '~/.tmux/plugins/tpm/tpm'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Catppuccin, nesta configuração, não está na lista &lt;code&gt;@plugin&lt;/code&gt; — foi clonado manualmente e carregado com um &lt;code&gt;run&lt;/code&gt; direto, apontando pro caminho do repositório:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/catppuccin/tmux.git ~/.config/tmux/plugins/catppuccin/tmux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run ~/.config/tmux/plugins/catppuccin/tmux/catppuccin.tmux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As duas formas funcionam — &lt;code&gt;@plugin 'catppuccin/tmux'&lt;/code&gt; via TPM também seria válido — mas o &lt;code&gt;run&lt;/code&gt; direto evita que uma atualização do TPM (&lt;code&gt;prefix + U&lt;/code&gt;) mexa acidentalmente na versão do tema enquanto ele ainda está sendo ajustado.&lt;/p&gt;

&lt;p&gt;Depois de salvar o arquivo e recarregar (&lt;code&gt;prefix + r&lt;/code&gt;), os plugins da lista &lt;code&gt;@plugin&lt;/code&gt; são instalados de dentro do próprio tmux com &lt;code&gt;prefix + I&lt;/code&gt; (maiúsculo). Para atualizar todos: &lt;code&gt;prefix + U&lt;/code&gt;. Para remover os que foram tirados da lista: &lt;code&gt;prefix + alt + u&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Plugins que Realmente Valem a Pena
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tmux-resurrect&lt;/strong&gt;: salva o estado completo de sessões, janelas e painéis em disco (&lt;code&gt;prefix + Ctrl+s&lt;/code&gt;) e restaura tudo depois (&lt;code&gt;prefix + Ctrl+r&lt;/code&gt;). Com &lt;code&gt;@resurrect-strategy-vim&lt;/code&gt;/&lt;code&gt;@resurrect-strategy-nvim&lt;/code&gt; setados como &lt;code&gt;session&lt;/code&gt;, ele delega a restauração do buffer do editor para a própria sessão de Vim/Neovim salva, em vez de só reabrir os arquivos:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  set -g @resurrect-capture-pane-contents 'on'
  set -g @resurrect-strategy-nvim 'session'
  set -g @resurrect-strategy-vim 'session'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tmux-continuum&lt;/strong&gt;: complementa o &lt;code&gt;tmux-resurrect&lt;/code&gt; salvando o estado automaticamente a cada alguns minutos, sem precisar lembrar de acionar o atalho manualmente:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  set -g @continuum-restore 'on'
  set -g @continuum-save-interval '15'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tmuxifier&lt;/strong&gt;: gerencia layouts de sessão pré-definidos por projeto (o equivalente, dentro do ecossistema de plugins do TPM, ao que ferramentas como &lt;code&gt;tmuxinator&lt;/code&gt;/&lt;code&gt;tmuxp&lt;/code&gt; fazem via script — assunto da próxima parte desta série).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tmux-battery&lt;/strong&gt; / &lt;strong&gt;tmux-cpu&lt;/strong&gt;: expõem &lt;code&gt;#{battery_percentage}&lt;/code&gt; e &lt;code&gt;#{cpu_percentage}&lt;/code&gt; como variáveis de formato, usadas pelos módulos &lt;code&gt;@catppuccin_status_battery&lt;/code&gt; e &lt;code&gt;@catppuccin_status_cpu&lt;/code&gt; na status bar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tmux-prefix-highlight&lt;/strong&gt;: mostra um indicador visual assim que o prefixo é pressionado — útil para confirmar que o tmux "ouviu" o &lt;code&gt;Ctrl+x&lt;/code&gt; antes de digitar o atalho seguinte, especialmente relevante quando o prefixo foi trocado do padrão.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tmux-online-status&lt;/strong&gt;: expõe &lt;code&gt;#{online_status}&lt;/code&gt;, para exibir se a máquina tem conectividade de rede direto na status bar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tmux-yank&lt;/strong&gt;: melhora a integração do copy mode com a área de transferência do sistema operacional (útil especialmente sobre SSH ou em WSL, onde essa integração não funciona por padrão).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Exemplo de &lt;code&gt;.tmux.conf&lt;/code&gt; Completo
&lt;/h2&gt;

&lt;p&gt;Juntando o conteúdo das seções anteriores num arquivo funcional (a configuração real usada no dia a dia, com o tema Catppuccin Frappe):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.tmux.conf

# Prefixo
set -g prefix C-x
unbind C-b
bind C-x send-prefix

# Reload
unbind r
bind r source-file ~/.tmux.conf

# Popups úteis
bind D display-popup -w 80% -h 80% -E "docker stats"
bind P display-popup -w 80% -h 80% -b rounded -s "fg=black" -S "fg=cyan" -E "zsh"

# Comportamento
set -g mouse on
set-option -g allow-rename off
set-option -g automatic-rename-format '#{b:pane_current_path}'
set -g default-terminal "tmux-256color"
set -g base-index 1
set -g pane-base-index 1
set -g renumber-windows on
set -g history-limit 50000
set -sg escape-time 10
set -g focus-events on
set -g display-time 2000

# Painéis e navegação
bind '"' split-window -v -c "#{pane_current_path}"
bind '%' split-window -h -c "#{pane_current_path}"
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
setw -g mode-keys vi
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel

# SSH: status bar laranja quando conectado remotamente
%if "#{SSH_CLIENT}"
set -g status-style "bg=#fe640b,fg=#ffffff"
set -g message-style "bg=#e64553,fg=#ffffff"
%else
set -g status-style "bg=#e78284,fg=#232634"
set -g message-style "bg=#ea999c,fg=#232634"
%endif
set-option -g status-position top
set -g status-interval 60

# Catppuccin
set -g @catppuccin_flavor "frappe"
set -g @catppuccin_window_status_style "rounded"
set -g @catppuccin_window_text "#H"
set -g @catppuccin_window_current_text "#W"
set -g @catppuccin_session_text " #I:#S"

# Plugins (TPM)
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @resurrect-capture-pane-contents 'on'
set -g @resurrect-strategy-nvim 'session'
set -g @resurrect-strategy-vim 'session'
set -g @plugin 'jimeh/tmuxifier'
set -g @plugin 'tmux-plugins/tmux-battery'
set -g @plugin 'tmux-plugins/tmux-cpu'
set -g @plugin 'tmux-plugins/tmux-prefix-highlight'
set -g @plugin 'tmux-plugins/tmux-online-status'
set -g @plugin 'tmux-plugins/tmux-yank'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @continuum-restore 'on'
set -g @continuum-save-interval '15'

run ~/.config/tmux/plugins/catppuccin/tmux/catppuccin.tmux

# Status bar
set -g status-left-length 100
set -g status-right-length 100
set -g status-left ""
set -g window-status-format ""
set -g window-status-current-format ""
set -gF status-left "#[fg=#babbf1]   ##H "
set -ag status-left "#{E:@catppuccin_status_session}"
set -ag status-left '#([ -n "$SSH_CLIENT" ] &amp;amp;&amp;amp; echo "#[fg=#ffffff bg=#fe640b bold] SSH #[default]") '
set -ag status-left "#[fg=#a6d189,bold] #(docker ps -q 2&amp;gt;/dev/null | wc -l | tr -d ' ')#[fg=#626880]/#[fg=#e78284]#(docker ps -aq --filter status=exited 2&amp;gt;/dev/null | wc -l | tr -d ' ') "
set -ag status-left "#[fg=#ef9f76] #(docker stats --no-stream --format '{{.CPUPerc}}' 2&amp;gt;/dev/null | sed 's/[^0-9.]//g' | awk '{sum+=$1} END {print int(sum*10)/10}')%% "
set -ag status-left "#[fg=#85c1dc] #(docker stats --no-stream --format '{{.MemUsage}}' 2&amp;gt;/dev/null | awk -F'/' 'NR==1{print $1}' | tr -d ' ') "
set -g status-right "#{E:@catppuccin_status_application}"
set -ag status-right "#{E:@catppuccin_status_uptime}"
set -agF status-right "#{E:@catppuccin_status_cpu}"
set -agF status-right "#{E:@catppuccin_status_ram}"
set -agF status-right "#{E:@catppuccin_status_load}"
set -agF status-right "#{E:@catppuccin_status_battery}"

# Painéis: borda com comando atual, branch git e horário
setw -g monitor-activity on
set -g visual-activity off
setw -g monitor-silence 0
set -g pane-border-lines single
set -g pane-border-status top
set -g pane-active-border-style "fg=#e5c890,bold"
set -g pane-border-style "fg=#006b6b"
set -g pane-border-format " #{?pane_active,#[fg=#a6d189 bold]▶ #[fg=#85c1dc bold],#[fg=#626880]}#{pane_current_command} #[align=right]#[fg=#ca9ee6]#(cd #{pane_current_path} &amp;amp;&amp;amp; git branch --show-current 2&amp;gt;/dev/null | sed '/^$/d; s/^/  /; s/$/ /') #[fg=#626880]· #[fg=#00ffff]%H:%M #[fg=#626880]· #[fg=#a6d189]#P "
set -g window-active-style "bg=default"
set -g window-style "bg=default"

run -b '~/.tmux/plugins/tpm/tpm'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Conclusão e Próximos Passos
&lt;/h2&gt;

&lt;p&gt;Com prefixo ajustado, tema Catppuccin aplicado e a status bar mostrando o que realmente importa no dia a dia — containers Docker, uso de CPU/memória, branch git do painel ativo, indicador de SSH — o tmux deixa de ser apenas funcional e passa a se adaptar ao fluxo de quem usa. No último artigo desta série, o assunto são exemplos reais de uso avançado: scripts de sessão automatizados, layouts fixos para desenvolvimento, sincronização de painéis entre múltiplos servidores e ajustes finos de performance.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo do tmux, por Jason Long — &lt;a href="https://commons.wikimedia.org/wiki/File:Tmux_logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/tmux/tmux/wiki/Plugins" rel="noopener noreferrer"&gt;tmux GitHub Wiki — Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmux-plugins/tpm" rel="noopener noreferrer"&gt;Tmux Plugin Manager (TPM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/catppuccin/tmux" rel="noopener noreferrer"&gt;catppuccin/tmux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmux-plugins/tmux-resurrect" rel="noopener noreferrer"&gt;tmux-resurrect&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>tmux</category>
      <category>linux</category>
      <category>cli</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
