O problema: "funcionava na minha máquina"#

Em DevOps, um dos maiores desafios é fazer uma aplicação se comportar da mesma forma em todos os ambientes: local, staging, produção, on-premise e nuvem. Diferenças em bibliotecas, versões do sistema operacional ou configuração quebram deploys que funcionavam na máquina de quem escreveu o código.

Os containers atacam esse problema pela raiz, e o Dockerfile é a peça que torna isso possível: uma receita versionada que descreve exatamente com o que sua aplicação é construída e como ela inicia.

Neste guia você vai ver o que é um Dockerfile, por que ele é especialmente valioso para aplicações externas e serviços próprios, como o Docker traz consistência a todo o ciclo de vida e as boas práticas que fazem diferença em produção, com exemplos que você pode copiar.

Documentação: Docker · Docker overview ↗

O que é um Dockerfile e por que ele importa#

Um Dockerfile é um arquivo de texto com instruções que o Docker executa para construir uma imagem. Nele você define:

  • A imagem base: Alpine, Debian, Ubuntu, distroless ou a imagem oficial da sua linguagem.
  • As versões do runtime (Node, Python, Java, Go).
  • Os pacotes e bibliotecas do sistema de que você precisa.
  • Os arquivos da aplicação que são copiados.
  • Variáveis de ambiente com valores padrão não sensíveis.
  • O usuário que executa o processo e o comando de inicialização.

O resultado é uma imagem: um artefato no formato padrão OCI que encapsula tudo o que a aplicação precisa para rodar, exceto o kernel, que é compartilhado com o host. Essa imagem pode ser versionada, escaneada, armazenada em um registry e executada em qualquer plataforma compatível.

Em outras palavras: sua aplicação deixa de depender de como cada servidor está configurado e passa a depender de uma definição explícita, que pode ser revisada em um pull request.

Documentação: Docker · Dockerfile reference ↗ · Docker · Docker overview ↗ · Open Container Initiative · Image spec ↗

O valor para aplicações externas: menos surpresas, mais portabilidade#

Com aplicações externas ou serviços personalizados (APIs próprias, workers batch, integrações, ETLs, dashboards internos, scrapers, microsserviços pequenos) o problema aumenta, porque eles costumam trazer:

  • Dependências difíceis ou muito específicas.
  • Binários ou runtimes pouco comuns.
  • Versões diferentes de Python, Node ou Java convivendo na mesma organização.
  • Bibliotecas do sistema que precisam ser instaladas manualmente.
  • Configuração espalhada entre ambientes.

Empacotar cada uma em sua própria imagem resolve isso: a aplicação roda igual na AWS, no Google Cloud, no Azure, no Kubernetes ou on-premise, não depende do que está instalado no servidor, cada mudança fica versionada e auditável, você pode replicar o ambiente exato em QA e uma mudança no host não a quebra. Casos típicos: microsserviços fora do core, cron jobs e tarefas batch, conectores com terceiros, ferramentas internas e aplicações legadas que você quer mover sem reescrever.

Documentação: Docker · Docker overview ↗

1. Builds reproduzíveis (e o que isso significa de verdade)#

A versão original deste artigo afirmava que duas pessoas que constroem o mesmo Dockerfile obtêm uma imagem idêntica. Não é bem assim: uma tag como node:22-alpine é atualizada com o tempo, os pacotes do sistema mudam e os timestamps variam. O que você consegue é que o ambiente fique definido de forma explícita; para chegar perto de builds reproduzíveis, é preciso fixar o que pode mudar.

dockerfile
# syntax=docker/dockerfile:1
# Fixe a tag (ou o digest @sha256:...) da imagem base
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# A imagem oficial do Node inclui o usuário sem privilégios node
USER node
EXPOSE 3000
CMD ["node", "src/index.js"]
Dockerfile para uma aplicação Node: dependências instaladas a partir do lockfile em um estágio separado, sem devDependencies, e execução como usuário não root.
  • Fixe a imagem base com uma tag específica e, se precisar de máxima estabilidade, com o digest.
  • Instale as dependências a partir do lockfile com npm ci ou o equivalente da sua linguagem.
  • Use um .dockerignore para que node_modules locais, .git ou arquivos .env não entrem no contexto de build.
  • Copie os manifestos de dependências antes do código, para aproveitar o cache de camadas.
text
# .dockerignore
.git
node_modules
.env
.env.*
!.env.example
dist
coverage
*.log
.dockerignore mínimo: exclui o que não deve chegar à imagem.

Para se aprofundar: Por que seu build funciona local, mas falha no deploy

Documentação: Docker · Pin base image versions ↗ · Docker · Building best practices ↗ · Docker · .dockerignore files ↗ · Docker · Reproducible builds ↗ · Node.js Docker image · Best practices ↗

2. Imagens imutáveis#

Uma imagem publicada não muda: suas camadas são identificadas pelo conteúdo e a imagem, pelo digest. Esse é o princípio da infraestrutura imutável: em vez de entrar no servidor para aplicar patches, você constrói uma imagem nova e substitui os containers.

Assim você evita ambientes contaminados por mudanças manuais, dependências instaladas à mão que ninguém documentou e configurações escondidas. Atenção: o sistema de arquivos de um container em execução pode, sim, ser modificado; o que é imutável é a imagem. Se algo muda dentro de um container, isso se perde quando ele é substituído, e é assim que deve ser.

A metodologia Twelve-Factor resume isso em separar build, release e run: o build produz o artefato uma única vez, o release o combina com a configuração de um ambiente e o run o executa.

Documentação: Open Container Initiative · Image spec ↗ · The Twelve-Factor App · Build, release, run ↗

3. Portabilidade, com uma ressalva de arquitetura#

A mesma imagem roda no Docker da sua estação de trabalho, no Kubernetes, no Amazon ECS ou EKS, no Google Cloud Run, no Azure Container Apps ou AKS e em servidores próprios. Essa é a promessa do build once, run anywhere, e em grande parte ela se cumpre porque todas essas plataformas aceitam imagens no formato OCI.

A ressalva: uma imagem é construída para uma arquitetura de CPU. Uma imagem só arm64, construída em um Mac com Apple Silicon, não inicia em um servidor x86_64, e o contrato do Cloud Run, por exemplo, exige executáveis Linux x86_64. Se você faz deploy em várias arquiteturas, como Raspberry Pi ou instâncias Graviton junto com x86, publique uma imagem multiplataforma.

bash
# Versão semântica imutável e duas arquiteturas em um único manifesto
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t registry.example.com/my-app:1.3.2 \
  --push .

# Mostra o digest e as plataformas publicadas
docker buildx imagetools inspect registry.example.com/my-app:1.3.2
Build multiplataforma com buildx e tag de versão imutável.

Documentação: Docker · Multi-platform builds ↗ · Google Cloud · Cloud Run container runtime contract ↗ · AWS · What is Amazon ECS ↗ · Microsoft · Azure Container Apps overview ↗

4. Integração natural com CI/CD#

Como a imagem é um artefato, o pipeline pode tratá-la como qualquer outro:

  • Construí-la uma única vez por commit.
  • Escanear vulnerabilidades nos pacotes.
  • Taguear com uma versão, por exemplo 1.3.2, ou com o SHA do commit.
  • Enviá-la para um registry (Amazon ECR, Google Artifact Registry, Azure Container Registry, Docker Hub).
  • Promover exatamente essa imagem de staging para produção e fazer o deploy automaticamente.

O segredo é promover a mesma imagem entre ambientes em vez de reconstruí-la em cada um. Assim, o que você testou em staging é, bit a bit, o que chega à produção.

Documentação: The Twelve-Factor App · Build, release, run ↗ · Kubernetes · Images ↗

Boas práticas: imagens mínimas e multi-stage#

Use imagens base pequenas (Alpine, variantes slim ou distroless): menos pacotes significam menos superfície de ataque e menos patches pendentes. Lembre que o Alpine usa musl em vez de glibc, o que pode afetar binários nativos; se tiver problemas, uma variante slim do Debian costuma ser a alternativa.

Com um build multi-stage você compila em uma imagem completa e copia apenas o resultado para uma imagem final mínima. As ferramentas de compilação não chegam à produção:

dockerfile
FROM golang:1.23 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app .

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
Binário Go estático (CGO_ENABLED=0) em uma imagem distroless que já roda como usuário nonroot.

Documentação: Docker · Multi-stage builds ↗ · Google · distroless images ↗ · Docker · Building best practices ↗

Boas práticas: configuração e secrets fora da imagem#

A imagem deve conter o código e suas dependências, não a configuração de um ambiente. Isso significa:

  • Não copiar arquivos .env para dentro da imagem.
  • Não fixar valores sensíveis com ENV nem ARG no Dockerfile: eles ficam visíveis no histórico da imagem.
  • Injetar a configuração em runtime com variáveis de ambiente, gerenciadores de secrets ou ConfigMaps e Secrets do Kubernetes.

Se o build precisa de um secret, como um token para um registry privado de pacotes, use os build secrets do BuildKit, que são montados somente durante aquele passo:

bash
# Dockerfile
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci

# O token não fica em nenhuma camada da imagem
docker build --secret id=npmrc,src=$HOME/.npmrc -t my-app:1.3.2 .
Montagem de um secret apenas durante o RUN que precisa dele.

Assim, a mesma imagem serve para dev, QA, staging e produção, e só a configuração externa muda.

Para se aprofundar: Variáveis de ambiente e DATABASE_URL em produção: guia completo

Documentação: Docker · Build secrets ↗ · Kubernetes · ConfigMaps ↗ · Kubernetes · Secrets ↗ · The Twelve-Factor App · Config ↗

Boas práticas: não execute como root#

Se você não indicar outra coisa, o processo do container roda como root. Se alguém comprometer a aplicação, terá mais margem para danificar o container e, diante de uma vulnerabilidade de escape, o host. Crie um usuário sem privilégios e mude para ele com USER:

dockerfile
# Alpine (BusyBox)
RUN addgroup -S app && adduser -S app -G app
USER app

# Debian / Ubuntu
RUN groupadd --system app && useradd --system --gid app app
USER app
No Alpine, adduser é a versão do BusyBox; no Debian e no Ubuntu, usa-se useradd.

Muitas imagens oficiais já trazem um usuário pronto, como node na imagem do Node ou nonroot no distroless. Essa prática reduz o impacto de uma vulnerabilidade nas suas dependências e segue os guias de hardening de containers mais comuns.

Documentação: Docker · USER instruction ↗ · Docker · Docker Engine security ↗ · Node.js Docker image · Best practices ↗

Boas práticas: versione cada build e defina a saúde#

Tags previsíveis facilitam o rollback: my-app:1.3.2 sempre aponta para a mesma coisa, enquanto latest muda a cada publicação. O Kubernetes recomenda não usar latest em produção porque fica difícil saber qual versão está rodando e voltar atrás; use tags de versão ou, melhor ainda, faça deploy por digest.

A saúde do container é definida principalmente pela plataforma. O Docker tem a instrução HEALTHCHECK, mas o Kubernetes a ignora e usa suas próprias probes de liveness, readiness e startup; o ECS e o Cloud Run também têm sua própria configuração. Exponha um endpoint de saúde na sua aplicação e configure-o onde for necessário.

Documentação: Kubernetes · Images ↗ · Docker · HEALTHCHECK instruction ↗ · Kubernetes · Liveness, readiness and startup probes ↗

Por que as nuvens adotaram os containers#

AWS, Google Cloud e Azure oferecem serviços gerenciados cuja unidade de deploy é uma imagem de container. Os motivos são práticos:

PropriedadeO que ela oferece
Inicialização rápida em comparação com uma VMEscalonamento horizontal mais ágil; o tempo real depende do tamanho da imagem e da aplicação
Isolamento por designCada serviço com suas dependências, sem conflitos entre eles
Substituição em vez de patchDeploys progressivos e rollbacks sem downtime, se a plataforma e a aplicação suportarem
Formato padrão OCIA mesma imagem serve em provedores diferentes

Em resumo: a imagem de container se tornou a unidade básica do deploy moderno.

Documentação: AWS · What is Amazon ECS ↗ · Google Cloud · Cloud Run container runtime contract ↗ · Microsoft · Azure Container Apps overview ↗

Checklist para um Dockerfile de produção#

  • Imagem base fixada por tag específica ou digest.
  • Dependências instaladas a partir do lockfile.
  • .dockerignore que exclui .git, node_modules e .env.
  • Build multi-stage: sem compiladores na imagem final.
  • Nem configuração nem secrets dentro da imagem; build secrets para o que o build precisar.
  • Processo rodando com um usuário não root.
  • Tag de versão imutável, arquitetura correta e a mesma imagem promovida entre ambientes.

Documentação: Docker · Building best practices ↗

Fontes e escopo

Documentação consultada em 25 de setembro de 2026. Os exemplos e critérios de decisão são propostas editoriais; adapte-os ao contrato da sua aplicação e valide-os no seu ambiente de testes autorizado.

Do design à decisão

Compare opções de nuvem

Confira preços, limites, condições e fontes de cada opção.

Abrir comparador