Docker para Desenvolvedores: Imagens, Contêineres e Compose

DevOps2026-10-12TryQuickToolBox

Você clonou um projeto, seguiu as instruções de configuração e bateu de frente com um obstáculo: uma biblioteca de sistema ausente, um banco de dados que não inicia ou uma versão do Node incompatível. O Docker existe para eliminar esse tipo de problema. Em vez de instalar serviços diretamente na sua máquina, você os descreve uma vez e os executa em contêineres isolados que se comportam da mesma forma em qualquer lugar.

Este guia explica as três ideias que você realmente precisa: imagens, contêineres e Compose. Ele se mantém prático, com comandos que você pode executar enquanto lê.

Imagens vs contêineres: o modelo mental

Uma imagem é um modelo somente leitura. Ela contém um snapshot de sistema de arquivos mais metadados, como o comando padrão e as portas expostas. Um contêiner é uma instância em execução (ou parada) dessa imagem com uma fina camada gravável por cima.

Se você vem da programação orientada a objetos, a analogia é próxima: uma imagem é uma classe, um contêiner é um objeto. Você pode iniciar muitos contêineres a partir de uma imagem, e alterações dentro de um contêiner não modificam a imagem.

ConceitoO que éComando típico
ImagemModelo imutáveldocker build, docker pull
ContêinerInstância em execuçãodocker run, docker ps
VolumeArmazenamento persistentedocker volume create
ComposeDefinição multi-contêinerdocker compose up

Sua primeira imagem com um Dockerfile

Um Dockerfile é uma receita. Cada instrução cria uma camada, e o Docker faz cache das camadas para que as reconstruções sejam rápidas quando nada mudou. Aqui está um pequeno exemplo em Node.js:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Dois hábitos importam aqui. Primeiro, copie os manifestos de dependências antes do restante do código-fonte para que a camada do npm ci seja armazenada em cache quando apenas o código da aplicação mudar. Segundo, prefira uma imagem base enxuta, como alpine, para reduzir tamanho e superfície de ataque.

Construa e execute:

docker build -t myapp:1.0 .
docker run --rm -p 3000:3000 myapp:1.0

A flag -p 3000:3000 mapeia a porta 3000 do host para a porta 3000 do contêiner. --rm exclui o contêiner quando ele é encerrado, o que mantém sua máquina organizada durante o desenvolvimento.

Comandos de contêiner do dia a dia

Estes cobrem a maior parte do trabalho diário:

Quando um contêiner sai imediatamente, leia os logs primeiro. As causas mais comuns são um CMD errado, uma variável de ambiente ausente ou o processo se vinculando a 127.0.0.1 em vez de 0.0.0.0 dentro do contêiner.

Por que o Compose existe

Uma aplicação real geralmente precisa de mais de um processo: uma API, um banco de dados, talvez um cache. Executar três comandos docker run com longas listas de flags é propenso a erros. O Docker Compose permite declarar todos os serviços em um único arquivo YAML e iniciá-los juntos.

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:

Observe que a API se conecta a db, não a localhost. O Compose cria uma rede privada onde cada serviço é acessível pelo seu nome de serviço. Esta é a fonte de confusão mais comum para iniciantes.

Inicie tudo com docker compose up, ou docker compose up -d para executar em segundo plano. Reconstrua após alterações de código com docker compose up --build. Encerre com docker compose down, e adicione -v se também quiser excluir os volumes nomeados.

Volumes, bind mounts e live reload

Contêineres são efêmeros; os dados dentro deles desaparecem quando removidos. Dois mecanismos resolvem isso:

  1. Volumes nomeados — gerenciados pelo Docker, ideais para arquivos de banco de dados. Eles persistem entre reinicializações de contêineres e são declarados na seção volumes:.
  2. Bind mounts — mapeiam um diretório do host para dentro do contêiner, ideais para código-fonte durante o desenvolvimento.
services:
  api:
    build: .
    volumes:
      - ./src:/app/src

Com um bind mount, editar arquivos no seu host é refletido dentro do contêiner. Combine isso com um observador de arquivos (nodemon, uvicorn --reload, air) e você obtém live reload sem reconstruir a imagem.

Um fluxo de trabalho prático de desenvolvimento

  1. Escreva um Dockerfile para sua aplicação e mantenha-o minimalista.
  2. Adicione um docker-compose.yml com a aplicação e suas dependências.
  3. Use bind mounts para o código-fonte e volumes nomeados para os dados.
  4. Mantenha segredos fora do arquivo; use um arquivo .env e referencie variáveis com ${VAR}.
  5. Adicione um .dockerignore contendo node_modules, .git e artefatos de build para acelerar as construções.
  6. Execute docker compose up --build e itere.

Se o seu serviço produz logs que você precisa inspecionar depois, lembre-se de que logs de contêiner são apenas texto. Ferramentas que analisam logs de servidor, como um analisador de logs nginx, combinam bem com contêineres, pois você pode redirecionar ou copiar logs para fora sem instalar nada no host.

Armadilhas comuns

FAQ

Qual é a diferença entre uma imagem e um contêiner?

Uma imagem é um modelo imutável contendo um sistema de arquivos e metadados. Um contêiner é uma instância em execução dessa imagem com uma camada gravável adicionada. Você pode iniciar muitos contêineres a partir de uma única imagem.

Preciso do Docker Compose para um único serviço?

Não. Um único comando docker run é suficiente para um contêiner. O Compose se torna valioso quando você tem múltiplos serviços, redes compartilhadas ou variáveis de ambiente que deseja declarar em um só lugar.

Como os contêineres se comunicam entre si?

O Compose coloca todos os serviços em uma rede compartilhada onde cada serviço é acessível pelo seu nome de serviço. Use esse nome como hostname, por exemplo db:5432, em vez de localhost.

O Docker recompensa um pequeno investimento inicial. Assim que imagens, contêineres e Compose fizerem sentido, integrar um colega de equipe ou trocar de máquina deixa de ser um ritual de configuração e se torna um único comando.