FlowingDev

Docker Compose, explicado: o controle remoto universal para seus contêineres

Aprenda como o Docker Compose usa um arquivo YAML para gerenciar aplicações com múltiplos contêineres, transformando comandos `docker run` complexos em um único blueprint compartilhável.

Testar a ferramenta: docker run pra Compose

Em uma frase

Docker Compose é uma ferramenta que permite definir e rodar aplicações com múltiplos contêineres usando um único arquivo YAML legível por humanos, em vez de um monte de comandos docker run longos e desajeitados.

O problema que ele resolve

No início, havia o docker run. E era... ok. Para rodar um contêiner, ele serve. Você digita seu comando, talvez com um mapeamento de porta e um volume, e boom, você tem um serviço rodando.

Mas o que acontece quando sua aplicação não é apenas um serviço? E se você tem um frontend web, uma API de backend, um banco de dados PostgreSQL e um cache Redis? De repente, você não está mais rodando um único comando. Você está fazendo malabarismos com quatro deles.

Você tem que criar manualmente uma network para que eles possam conversar entre si. Você tem que garantir que não vai acidentalmente mapear todos eles para a mesma porta do host. Você tem que lembrar o comando docker run exato e muito longo para cada serviço, com todas as suas variáveis de ambiente e montagens de volume. Se quiser compartilhar essa configuração com um novo colega de equipe, você escreve um arquivo README.md gigante que é basicamente uma reza para que ele copie e cole tudo corretamente. É uma receita para o caos, erros de digitação e o clássico dilema "bem, na minha máquina funciona!".

É esse caos que o Docker Compose, nascido de um projeto chamado Fig que o Docker adquiriu em 2014, foi projetado para domar. Trata-se de passar de gerenciar contêineres individuais como "bichos de estimação" para orquestrar um "rebanho" de aplicações inteiro. Em vez de emitir comandos imperativos (execute isso, depois aquilo, conecte-os assim...), você escreve um único arquivo declarativo (eis a aparência de toda a minha pilha de aplicações) e deixa o Compose cuidar dos detalhes bagunçados para torná-lo realidade.

Como funciona por debaixo dos panos

Em sua essência, o Docker Compose é um tradutor. Ele traduz um arquivo YAML descritivo em uma série de chamadas de API para o Docker Engine. É o "mostre, não conte" do gerenciamento de contêineres.

De flags do docker run para chaves YAML

Digamos que você queira rodar um banco de dados PostgreSQL. O comando docker run pode parecer uma sopa de letrinhas de um filme de hacker:

docker run --name my-postgres-db \
  -e POSTGRES_PASSWORD=supersecret \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  --restart unless-stopped \
  -d postgres:14

Este comando tem um nome, uma variável de ambiente para a senha, um mapeamento de porta, um volume nomeado para persistir dados, uma política de reinicialização, e roda em segundo plano (-d).

Agora, vamos ver o equivalente no Docker Compose em um arquivo docker-compose.yml:

version: '3.8' # Especifica a versão do arquivo Compose

services:
  db: # Este é um nome amigável para o nosso serviço
    image: postgres:14
    container_name: my-postgres-db
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: supersecret
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata: # Declaramos o volume nomeado aqui

Viu o paralelo? As flags da linha de comando se tornaram chaves YAML limpas e organizadas. Não é apenas mais bonito; é uma definição do seu serviço que pode ser versionada, compartilhada e muito menos propensa a erros.

Flag do docker run Chave do docker-compose.yml O que faz
postgres:14 (a imagem) image: postgres:14 Especifica a imagem de contêiner a ser usada.
--name my-postgres-db container_name: my-postgres-db Dá um nome específico ao contêiner.
-p 5432:5432 ports: ["5432:5432"] Mapeia uma porta do host para o contêiner.
-v pgdata:/var/lib/... volumes: [pgdata:/var/lib/...] Monta um volume para dados persistentes.
-e VAR=value environment: { VAR: value } Define variáveis de ambiente dentro do contêiner.
--restart unless-stopped restart: unless-stopped Define a política de reinicialização do contêiner.

A Mágica do Networking

É aqui que o Compose realmente brilha. Se você adicionasse um serviço de aplicação web a esse mesmo arquivo YAML, assim:

services:
  db:
    # ... (nossa configuração do postgres de cima)
  
  api:
    image: my-cool-api:latest
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://user:supersecret@db:5432/mydatabase
    depends_on:
      - db

Quando você roda docker-compose up, o Compose faz algo brilhante: ele cria uma rede virtual dedicada para sua aplicação. Tanto o contêiner db quanto o api são anexados a essa rede.

Agora, o serviço api pode se conectar ao banco de dados usando o hostname db — o mesmo nome que demos ao serviço no arquivo YAML! Chega de procurar endereços IP ou usar flags --link frágeis. O Compose cuida de toda a resolução de DNS dentro de sua rede privada. É assim que seu contêiner da aplicação web pode sussurrar palavras doces (ou, você sabe, queries SQL) para o seu contêiner de banco de dados de forma segura e confiável.

O Ciclo de Vida do Compose

O comando docker-compose é o seu painel de controle para toda a pilha de aplicações.

  • docker-compose up: Este é o comando principal. Ele lê seu docker-compose.yml, verifica o estado do seu sistema e faz o que for necessário para que ele corresponda ao arquivo. Ele criará redes, construirá imagens (se você especificou uma instrução build), criará volumes e iniciará todos os contêineres de serviço. Se você rodá-lo novamente, ele verificará inteligentemente por mudanças e recriará apenas os contêineres que precisam de atualização.
  • docker-compose down: O oposto amigável do up. Ele para e remove todos os contêineres e, por padrão, também remove a rede que criou. É o botão "limpe minha bagunça". Para um recomeço totalmente limpo, docker-compose down -v também remove os volumes nomeados.
  • docker-compose ps: Mostra o status de todos os contêineres gerenciados pelo seu arquivo Compose.
  • docker-compose logs -f: Acompanha os logs de todos os seus serviços em um único fluxo conveniente, com código de cores por serviço. Inestimável para debugar.

Histórias do mundo real

A Saga do "Na Minha Máquina Funciona"

Devon acabou de terminar uma nova feature para o dashboard de analytics da sua empresa, que usa um backend em Node.js e um banco de dados MongoDB. Funciona perfeitamente no seu laptop. Ele sobe o código e diz a um novo contratado, Sam, para baixá-lo e rodar. O README de Devon tem dois comandos docker run massivos. Sam, sendo humano, comete um erro de digitação no caminho do volume do banco de dados. A aplicação inicia, mas não consegue salvar nada. Sam passa três horas debugando o código da aplicação, pensando que o bug está lá, enquanto Devon insiste que está tudo bem. Finalmente, eles compartilham a tela e encontram o erro de digitação. A frustração é alta.

A lição: Se eles tivessem usado um docker-compose.yml, o onboarding teria sido um único comando docker-compose up. Todo o ambiente — portas, volumes, variáveis de ambiente — seria idêntico, eliminando toda uma classe de erros de configuração e transformando meio dia de debug em uma inicialização de 30 segundos.

O Pesadelo do Pipeline de CI/CD

Uma equipe de DevOps estava construindo um pipeline de integração contínua para testar sua aplicação de microsserviços. A aplicação tinha um servidor web, um API gateway, um serviço de usuário e um cache Redis. Seu Jenkinsfile era um script horrendo de comandos shell, com quatro chamadas docker run separadas e um monte de comandos sleep 30 na esperança de que o banco de dados estivesse pronto antes que a API tentasse se conectar a ele. Os testes eram instáveis; às vezes passavam, às vezes falhavam porque o sleep não era longo o suficiente.

A lição: Eles reescreveram o pipeline para usar o Docker Compose. O Jenkinsfile foi simplificado para docker-compose -f docker-compose.ci.yml up -d. Eles usaram depends_on com condition: service_healthy para garantir que a API só iniciasse depois que o banco de dados passasse em sua verificação de saúde interna. Os testes instáveis desapareceram da noite para o dia. O Compose forneceu uma maneira confiável e declarativa de definir e lançar o ambiente de teste exato necessário, todas as vezes.

O Sidecar Esquecido

Uma desenvolvedora, Maria, implantou sua aplicação principal em Python em um contêiner Docker. Algumas semanas depois, seu gerente pediu que ela adicionasse monitoramento detalhado de desempenho. A melhor maneira era rodar um exportador do Prometheus como um "sidecar" — um segundo contêiner que vive ao lado da aplicação principal, compartilhando sua rede para coletar métricas. Maria o configurou com outro comando docker run. Mas agora ela tinha que lembrar de iniciar, parar e reiniciar dois contêineres toda vez que fazia uma alteração. Um dia, ela reiniciou a aplicação mas esqueceu o sidecar, e eles perderam um dia inteiro de métricas.

A lição: Maria refatorou sua configuração para um docker-compose.yml. Ela definiu sua aplicação Python como um serviço e o exportador do Prometheus como um segundo. Agora, docker-compose up trazia ambos à vida, e docker-compose down os aposentava juntos. Eles eram gerenciados como uma única unidade lógica, como deveriam ser. O Compose torna padrões como sidecars triviais de gerenciar.

Erros e armadilhas comuns

  • Assumir que depends_on espera pela prontidão. Por padrão, depends_on espera apenas o contêiner da dependência iniciar, não que a aplicação dentro dele esteja totalmente inicializada e pronta para aceitar conexões. Sua API pode iniciar antes que seu banco de dados esteja pronto para ser consultado, causando um crash. A solução real é usar a instrução healthcheck na definição do seu serviço de banco de dados e, em seguida, usar a sintaxe longa de depends_on com condition: service_healthy.
  • Fazer commit de segredos no Git. É incrivelmente tentador simplesmente colocar POSTGRES_PASSWORD: minha-senha-secreta diretamente no docker-compose.yml. Não faça isso. Qualquer pessoa com acesso ao seu repositório agora tem a senha do seu banco de dados. A maneira correta é usar um arquivo .env (que você adiciona ao .gitignore) e referenciar variáveis como ${POSTGRES_PASSWORD} no seu arquivo Compose.
  • Confundir bind mounts e named volumes. Um caminho começando com . ou / (ex: ./my-app:/app) é um bind mount, que mapeia um diretório da sua máquina host. Isso é ótimo para desenvolvimento, para que as alterações no código sejam refletidas instantaneamente. Um caminho começando apenas com um nome (ex: db-data:/var/lib/mysql) é um volume nomeado gerenciado pelo Docker. Isso é melhor para persistir dados como um banco de dados, pois é independente de plataforma e gerenciado pelo Docker engine. Usar o tipo errado pode levar a problemas de permissão ou a dados que não são tão portáteis quanto você pensa.
  • Usá-lo para produção em larga escala. O Docker Compose é fantástico para desenvolvimento, testes e até mesmo pequenas implantações de produção em um único host. Mas ele NÃO é um substituto para um orquestrador de contêineres completo como Kubernetes ou Docker Swarm. Ele não tem conceito nativo de clusters com múltiplos nós, auto-recuperação, balanceamento de carga entre nós ou atualizações graduais sofisticadas. Conheça seus limites.

Por que ele deve estar no seu radar

Você deve pensar no Docker Compose no momento em que sua aplicação envolve mais de uma parte móvel.

  • Você está construindo uma aplicação web com um banco de dados separado? Use o Compose.
  • Você precisa rodar sua aplicação, um cache e um worker em segundo plano? Use o Compose.
  • Você quer dar aos seus colegas de equipe uma maneira de rodar toda a pilha de desenvolvimento com um único comando? Use o Compose.
  • Você está configurando um pipeline de CI que precisa levantar um ambiente temporário para testes? Use o Compose.

É a ferramenta padrão da indústria para desenvolvimento local baseado em contêineres. Aprendê-la é o próximo passo natural depois do docker run, e é a porta de entrada para entender o mundo mais complexo da orquestração de aplicações. Ele preenche a lacuna entre rodar um único contêiner e rodar um sistema distribuído completo.

Aprofunde-se

  • Docker Docs: Compose file version 3 reference - A especificação oficial e fonte da verdade para todas as chaves e sintaxes disponíveis.
  • Docker Docs: Overview of Docker Compose - Uma introdução de alto nível dos próprios criadores.
  • YAML Spec - Como todos os arquivos docker-compose.yml são YAML, entender a sintaxe (mapas, listas, âncoras) pode ser muito útil.
  • Awesome Docker - Uma lista com curadoria de recursos sobre Docker, muitos dos quais incluem tutoriais e exemplos de arquivos docker-compose.yml para várias stacks.

Teoria feita. Hora de pôr a mão na massa — 100% no seu navegador.

Testar a ferramenta: docker run pra Compose