Como configurar um ambiente de desenvolvimento com containers — do zero ao deploy
A maior parte dos tutoriais que você vê pela internet começa com uma instalação limpa e termina com um comando mágico que funciona perfeitamente. Na prática, isso raramente acontece. Um exemplo de tecnologia bem executado não é aquele que funciona na primeira tentativa, mas sim aquele que mostra onde as coisas costumam dar errado e como resolver isso sem perder duas horas rastreando logs intermináveis. Vou explicar isso pelo método, não pela definição teórica. O primeiro passo é entender que o problema principal em containers não é subir o serviço. O problema é fazer com que o serviço persista dados, comunique-se corretamente com outros serviços e rode de forma previsível quando você reinicia a máquina. A maioria dos guias ignora essas três camadas e isso gera dor de cabeça depois.
Comece pelo arquivo de configuração. Eu já vi gente rodar tudo com comandos soltos no terminal porque achava que docker-compose era complic demais. Foi até funcionar. Mas no momento em que você precisa reproduzir o ambiente em outra máquina ou voltar atrás numa versão anterior, esquece. Estruture isso com volumes nomeados, redes definidas e variáveis de ambiente em arquivo próprio. Nada de senhas hardcoded.
Configurando volumes e rede — o que ninguém explica
Quando você monta um volume em um container de banco de dados, o Docker por padrão cria esse volume dentro de /var/lib/docker/volumes. Isso significa que se você limpar o Docker com o comando docker system prune, vai perder os dados. Sem aviso. Eu perdi seis meses de dados de desenvolvimento dessa forma. A solução é usar bind mounts apontando para um diretório específico no seu host, como ./data/postgres, e garantir que esse diretório exista antes de subir o stack. A rede também é mais importante do que parece. Se você não definir uma rede customizada, os containers se comunicam pelo DNS padrão do Docker, que funciona, mas não dá controle sobre timeout, resolução ou isolamento. Defina uma rede bridge com nome explícito e conecte todos os serviços nela. Containers em redes diferentes não se veem, o que é útil para separar produção de desenvolvimento sem precisar de máquinas diferentes.
Handle de erro e variáveis de ambiente
Variáveis de ambiente em containers são a forma padrão de passar configuração. Mas há um detalhe que causa confusão: se você definir uma variável no docker-compose.yml e ela não existir no sistema hospedeiro, o valor vazio é passado para o container. Alguns serviços tratam variável vazia como configuração nula. Outros travam. Depende do software. Eu tive um problema específico com um serviço de fila que lia uma variável DATABASE_URL. Quando eu rodava localmente sem essa variável definida, o container subia normalmente mas entrava em loop de retry a cada cinco segundos, consumindo CPU e enchendo o log. A solução não foi adicionar um valor fictício. Foi criar um arquivo .env separado para cada ambiente — .env.local para desenvolvimento, .env.staging para teste, .env.prod para produção — e colocar cada um no .gitignore. Assim nenhum arquivo com credenciais reais acaba no repositório.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Health checks e dependência entre serviços
O recurso mais subutilizado em compose é o healthcheck. Sem ele, você sobe um container que depende de outro e espera que o segundo já esteja pronto. O problema é que "subido" não significa "funcionando". Um banco de dados pode estar rodando mas ainda carregando dados na memória. Um servidor de aplicação pode estar acessível mas ainda compilando rotas. Configure healthcheck com intervalos razoáveis. Cada cinco segundos com timeout de dez segundos e três retries é um ponto de partida seguro para a maioria dos serviços. Use start_period para dar tempo de inicialização longa, especialmente para Java e .NET que podem levar mais de um minuto.
Agora vou ser objetivo sobre as limitações. Containers não resolvem problemas de arquitetura. Se seu serviço consome muita memória e você não dimensionou corretamente, o container vai ser morto pelo OOM killer do kernel antes de qualquer coisa. Eles também não simplificam debugging. Ler logs de um container que crashed é diferente de ler logs de um processo nativo, e ferramentas como kubectl logs ou docker logs têm overhead que pode esconder informações importantes nos primeiros segundos de boot. Se o seu projeto é pequeno, com um único serviço e banco de dados local, containers podem serkill. Um ambiente virtual Python ou um serviço PostgreSQL instalado diretamente no SO roda mais rápido e consome menos recursos. O ganho real aparece quando você tem três ou mais serviços interagindo, precisa de portabilidade entre máquinas ou quer simular um ambiente de produção com fidelidade.
Deploy e persistência
Para colocar isso em produção, o próximo passo é garantir que os dados sobrevivam a reinicializações e que as imagens sejam construídas de forma determinística. Use Dockerfiles com camadas estáveis — copie requirements primeiro, instale dependências depois, copie código por último. Isso permite cache de build e evita recompilações desnecessárias. Imagens pequenas são melhores para deploy, mas não sacrifiquem legibilidade por minimizar camadas. Um exemplo de tecnologia funcionando de verdade é aquele que mostra o fluxo completo: desenvolvimento local com dados isolados, testes com variables de ambiente diferentes, e deploy com as mesmas definições de rede e volume. Se você precisa mudar algo na configuração, altera um arquivo e sobe o stack novamente, sem reconstruir nada que não mudou. Isso economiza tempo e reduz erros de configuração entre ambientes.
O link para documentação oficial do Docker Compose está em docs.docker.com/compose. A versão atual do compose suporta secrets, config e build cache integrado, o que elimina várias gambiarras que eram necessárias há dois anos. Vale a pena atualizar se você ainda está usando arquivos de configuração antigos com syntax deprecated.