Docker: Fundamentos do Dockerfile
Última atualização: 2026-08-26
Um Dockerfile é o "código fonte" de uma imagem — escrevendo um Dockerfile, você pode construir uma imagem de aplicação reproduzível com um único comando.
1. O Que Você Vai Aprender
- Sintaxe Básica e Estrutura de um Dockerfile
- Política para selecionar a imagem base usando a cláusula "FROM"
- RUN: Melhores Práticas para Executar Comandos de Build
- A Diferença Entre CMD e ENTRYPOINT
- Configurando o Contexto e .dockerignore
2. Uma História Real de uma Pessoa Desenvolvedora Python
(1) Ponto de Dor: Ter que configurar manualmente o ambiente a cada implantação
Alice escreveu uma aplicação web Python. Toda vez que ela implanta, precisa instalar manualmente o Python, configurar um ambiente virtual, instalar dependências e copiar o código. Ela precisa repetir esse processo três vezes — uma para o servidor de teste, uma para o ambiente de pré-produção e uma para o servidor de produção — totalizando três repetições, cada uma com pequenas variações.
(2) Soluções para Automação com Dockerfile
Bob disse: "Escreva um Dockerfile, 'empacote' sua aplicação em uma imagem e então você poderá executá-la em qualquer lugar com um único comando."
# Um Dockerfile simples para uma aplicação Flask
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
(3) Benefício: Construir Uma Vez, Executar em Qualquer Lugar
Depois que Alice construiu a imagem usando docker build -t myapp:1.0 ., todos os três ambientes rodaram a mesma imagem, reduzindo o tempo de implantação de 30 minutos para 3 minutos e eliminando inconsistências de ambiente.
3. Estrutura Básica de um Dockerfile
Um Dockerfile é um arquivo de texto puro que contém uma sequência de instruções para construir uma imagem. Cada instrução constrói uma camada da imagem.
graph LR
DF["Dockerfile<br/>Sequência de Instruções"] -->|docker build| IMG["Imagem<br/>Sobreposição Somente Leitura"]
IMG -->|docker run| CTN["Contêiner<br/>Camada gravável + Camada Somente Leitura"]
(1) Momento de Execução das Instruções
| Momento | Comando | Descrição |
|---|---|---|
| Durante o build | FROM / RUN / COPY / ADD / ARG |
Gerar camada da imagem |
| Tempo de execução | CMD / ENTRYPOINT / ENV / EXPOSE / USER |
Define o comportamento do contêiner |
(2) Modelo Básico de Dockerfile
# 1. Imagem base
FROM python:3.12-slim
# 2. Definir diretório de trabalho
WORKDIR /app
# 3. Copiar arquivo de dependências primeiro (otimização de cache)
COPY requirements.txt .
# 4. Instalar dependências
RUN pip install --no-cache-dir -r requirements.txt
# 5. Copiar código da aplicação
COPY . .
# 6. Definir o comando padrão
CMD ["python", "app.py"]
4. FROM: Selecionar uma imagem base
FROM é a primeira instrução em um Dockerfile; ela especifica a imagem base para o build.
(1) Estratégia de Seleção de Imagem Base
| Estratégia | Imagem | Tamanho | Caso de Uso |
|---|---|---|---|
| Imagem Oficial de Linguagem | python:3.12-slim |
155 MB | Projeto Python |
| Variante Alpine | python:3.12-alpine |
50 MB | Espaço em disco extremamente baixo |
| Build multi-estágio | golang:1.22 → alpine |
12 MB | Linguagens compiladas Go/Rust |
| SO Minimalista | debian:bookworm-slim |
74 MB | Requer um ambiente personalizado |
▶ Exemplo: O Dockerfile Mais Simples (Dificuldade: ⭐)
# Dockerfile mínimo: apenas imprime hello
FROM alpine:3.19
CMD ["echo", "Hello from Docker!"]
# Construir e executar
docker build -t hello:1.0 .
docker run --rm hello:1.0
Hello from Docker!
5. RUN: Executar o comando de build
RUN Executa um comando durante o build e grava o resultado em uma nova camada da imagem.
(1) Dois Formatos
| Formato | Sintaxe | Características |
|---|---|---|
| Formato Shell | RUN apt-get install nginx |
Executa /bin/sh -c por padrão; suporta pipes |
| Modo Exec | RUN ["apt-get", "install", "nginx"] |
Executa diretamente sem iniciar um shell |
▶ Exemplo: RUN para instalar dependências (Dificuldade: ⭐⭐)
# Melhor prática: combinar comandos RUN para reduzir camadas
FROM debian:bookworm-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
nginx && \
rm -rf /var/lib/apt/lists/*
apt-get em um único RUN para reduzir o número de camadas da imagem. rm -rf /var/lib/apt/lists/* limpa o cache APT para reduzir o tamanho da imagem.
(2) Princípios da Fusão em Cadeia RUN
| Método | Resultado | Observações |
|---|---|---|
Múltiplos RUN |
Cada entrada cria uma camada | Muitas camadas, tamanho grande |
Fundir RUN && |
Uma camada por trilha | Poucas camadas, tamanho compacto |
| Limpar Cache | rm -rf apt/lists |
Limpa cache dentro do mesmo nível |
| Limpar separadamente | Próximo RUN rm |
Inválido — a camada anterior já curou |
# Ruim: cria 2 camadas, cache apt é gravado na camada 1
RUN apt-get update
RUN apt-get install -y nginx
# Bom: cria 1 camada, cache limpo na mesma camada
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
6. CMD e ENTRYPOINT
Ambos CMD e ENTRYPOINT definem os comandos a serem executados quando o contêiner é iniciado, mas eles se comportam de forma diferente.
(1) Comparação dos Três Comandos de Inicialização
| Dimensão | CMD |
ENTRYPOINT |
|---|---|---|
| Propósito | Fornecer comandos padrão | Definir um ponto de entrada fixo |
| Pode ser sobrescrito | Parâmetro docker run sobrescreve diretamente |
Requer --entrypoint para sobrescrever |
| Combinação | Pode ser usado com ENTRYPOINT | Pode ser usado com argumentos de linha de comando CMD |
| Múltiplos | Apenas o último tem efeito | Apenas o último tem efeito |
(2) Os Três Formatos de CMD
| Formato | Sintaxe | Nível de Recomendação | Descrição |
|---|---|---|---|
| Formato Exec | CMD ["python", "app.py"] |
⭐⭐⭐ | Executa diretamente; sinais são passados corretamente |
| Formato Shell | CMD python app.py |
⭐ | Como processo filho de /bin/sh -c, SIGTERM não é passado |
| Formato de Parâmetro | CMD ["--port", "8080"] |
⭐⭐ | Usar com ENTRYPOINT |
▶ Exemplo: A Diferença Entre CMD e ENTRYPOINT (Dificuldade: ⭐⭐)
# Dockerfile com CMD: comando pode ser facilmente sobrescrito
FROM alpine:3.19
CMD ["echo", "Hello default"]
# Padrão: executa CMD
docker run --rm test-cmd
# Saída: Hello default
# Sobrescrever CMD com comando personalizado
docker run --rm test-cmd echo "Custom message"
# Saída: Custom message
# Dockerfile com ENTRYPOINT: comando permanece, args são anexados
FROM alpine:3.19
ENTRYPOINT ["echo"]
CMD ["Hello default"]
# Padrão: executa ENTRYPOINT + CMD
docker run --rm test-entry
# Saída: Hello default
# Anexar argumentos (não sobrescreve ENTRYPOINT)
docker run --rm test-entry "Custom message"
# Saída: Custom message
# Sobrescrever ENTRYPOINT (raramente necessário)
docker run --rm --entrypoint sh test-entry -c "ls /"
▶ Exemplo: Combinação ENTRYPOINT + CMD (Dificuldade: ⭐⭐⭐)
Esta é a abordagem de melhor prática — ENTRYPOINT especifica o programa a ser executado e CMD fornece os argumentos padrão:
# Padrão Entrypoint + CMD
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "5000"]
# Padrão: usa argumentos CMD
docker run --rm myapp
# Equivalente a: python app.py --host 0.0.0.0 --port 5000
# Sobrescrever apenas os argumentos
docker run --rm myapp --port 8080
# Equivalente a: python app.py --port 8080
7. Configurando o Contexto e .dockerignore
(1) Configurando o Contexto
O último parâmetro de docker build, ., não se refere ao "diretório atual", mas sim ao contexto de build — o Cliente Docker envia todos os arquivos neste diretório para o Daemon.
graph LR
CTX["Contexto de Build<br/>(. Índice do Diretório)"] -->|Enviar Arquivo| D["Docker Daemon"]
D -->|Dockerfile em COPY/ADD| IMG["Camadas da Imagem"]
node_modules no diretório, 1 GB será enviado ao daemon durante o build (mesmo que o Dockerfile não faça COPY dele). .dockerignore pode excluir arquivos desnecessários.
▶ Exemplo: Como .dockerignore Funciona (Dificuldade: ⭐⭐)
# .dockerignore - excluir arquivos do contexto de build
node_modules
.git
__pycache__
*.pyc
.env
Dockerfile
docker-compose*.yml
README.md
.vscode
(2) Por que a diretiva COPY para arquivos de dependência deve vir antes da diretiva COPY para código fonte?
# Bom: taxa de mudança de dependência < taxa de mudança de código
# Quando o código muda, a camada de dependência usa cache
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# Ruim: qualquer mudança de arquivo invalida a camada pip install
COPY . .
RUN pip install -r requirements.txt
| Cenário de Mudança | Versão Boa | Versão Ruim |
|---|---|---|
| Modificar apenas código | ✅ Usa cache pip (segundos) | ❌ Reconstrói pip (minutos) |
| Mudar Dependências | ✅ Reconstrói camada pip (necessário) | ❌ Reconstrói camada pip (como acima) |
8. Parâmetros Comuns para docker build
| Parâmetro | Função | Exemplo |
|---|---|---|
-t |
Nome da Imagem: Tag | -t myapp:1.0 |
-f |
Especificar caminho do Dockerfile | -f Dockerfile.prod . |
--build-arg |
Passar parâmetros de build | --build-arg VERSION=2.0 |
--no-cache |
Não usar cache | --no-cache |
--target |
Construir até um estágio especificado | --target builder |
--platform |
Especificar Plataforma Alvo | --platform linux/arm64 |
▶ Exemplo: Construir uma imagem e visualizar o histórico de camadas (Dificuldade: ⭐⭐)
# Construir com tag
docker build -t myapp:1.0 .
# Visualizar camadas da imagem
docker history myapp:1.0
9. Exemplo Completo: Escrevendo um Dockerfile para uma Aplicação Flask
# ============================================
# Dockerfile para uma aplicação web Flask
# Demonstra: FROM, WORKDIR, COPY, RUN, CMD
# ============================================
# Usar imagem oficial Python slim
FROM python:3.12-slim
# Definir diretório de trabalho dentro do contêiner
WORKDIR /app
# Copiar arquivo de dependências primeiro (otimização de cache)
COPY requirements.txt .
# Instalar dependências (limpar cache na mesma camada)
RUN pip install --no-cache-dir -r requirements.txt
# Copiar código fonte da aplicação
COPY . .
# Expor a porta da aplicação (apenas documentação)
EXPOSE 5000
# Executar a aplicação Flask
CMD ["python", "app.py"]
# Construir a imagem
docker build -t flask-app:1.0 .
# Executar o contêiner
docker run -d -p 5000:5000 --name my-flask flask-app:1.0
# Testar a aplicação
curl http://localhost:5000
# Visualizar tamanho e camadas da imagem
docker images flask-app
docker history flask-app:1.0
# docker images flask-app
REPOSITORY TAG IMAGE ID SIZE
flask-app 1.0 a1b2c3d4e5f6 180MB
# docker history flask-app:1.0
IMAGE CREATED CREATED BY SIZE
a1b2c3d4e5f6 5 seconds ago CMD ["python" "app.py"] 0B
<missing> 5 seconds ago COPY . . 2.5kB
<missing> 5 seconds ago RUN pip install --no-cache-dir... 45MB
<missing> 5 seconds ago COPY requirements.txt . 58B
<missing> 5 seconds ago WORKDIR /app 0B
❓ Perguntas Frequentes
P: Por que devemos fazer COPY dos arquivos de dependência primeiro e COPY do código fonte depois? R: Para aproveitar o mecanismo de cache de camadas. As dependências mudam com muito menos frequência do que o código. Fazendo COPY dos arquivos de dependência e instalando-os primeiro, esta camada é armazenada em cache; quando apenas o código é modificado depois, a camada de dependência usa diretamente o cache, reduzindo o tempo de build de minutos para segundos. Se todos os arquivos fossem copiados primeiro, qualquer mudança de arquivo invalidaria o cache da camada pip install.
P: CMD e ENTRYPOINT podem ser usados juntos? R: Sim, esta é a abordagem recomendada. ENTRYPOINT define um executável fixo (como
python app.py), enquanto CMD fornece argumentos padrão (como--port 5000). Os argumentos passados paradocker runsobrescrevem CMD, mas não ENTRYPOINT, permitindo uma configuração de "executável fixo + argumentos flexíveis".
P: O que significa "contexto de build"? R: O
.no final do comandodocker buildespecifica o diretório de contexto de build. O cliente Docker empacota todos os arquivos neste diretório e os envia ao daemon. Os comandosCOPYeADDno Dockerfile só podem referenciar arquivos dentro do contexto de build. Use.dockerignorepara excluir arquivos desnecessários, o que acelera o build e reduz o tamanho do contexto de build.
P: Como escrevo um arquivo .dockerignore? *R: A sintaxe é a mesma do .gitignore. Você deve excluir: node_modules, .git, pycache, .env, .pyc e o próprio Dockerfile. Regra prática: Exclua quaisquer arquivos que não precisem ser copiados para a imagem para reduzir o tamanho do contexto e acelerar o build.
P: Como soluciono uma falha de build? R: Siga estas três etapas: ① Revise a mensagem de erro — o Docker indicará o comando com falha e o número da linha; ② Verifique o contexto — verifique se o arquivo especificado por
COPYexiste; ③ Depure interativamente —docker run -it <última-camada-bem-sucedida> bashentre na última camada de imagem bem-sucedida para solucionar manualmente.
P: O que devo fazer se uma linha em RUN for muito longa? R: Use
\para continuar a linha e&¶ encadear comandos. Esta é a maneira padrão de escrever um Dockerfile, que reduz o número de camadas enquanto mantém a legibilidade. Por exemplo:RUN apt-get update && \+apt-get install -y nginx && \+rm -rf /var/lib/apt/lists/*.
📖 Resumo
- Um Dockerfile é o "código fonte" de uma imagem; cada instrução gera uma camada somente leitura
- FROM: Escolha uma imagem base: "slim" oferece boa compatibilidade, enquanto "alpine" é mínimo mas pode ter problemas de compatibilidade
- Use o comando de fusão RUN para reduzir o número de camadas e limpe o cache dentro de cada camada para reduzir o tamanho do arquivo
- CMD fornece um comando padrão (que pode ser sobrescrito), enquanto ENTRYPOINT define um ponto de entrada fixo (que é difícil de sobrescrever)
- A combinação ENTRYPOINT + CMD é uma melhor prática: um programa fixo + parâmetros flexíveis
- COPY arquivos de dependência antes do código fonte e use cache de camadas para acelerar o build
📝 Exercícios
- Problema Básico (Dificuldade: ⭐): Escreva um Dockerfile para uma aplicação Node.js, usando
node:20-alpinecomo imagem base,npm installpara instalar dependências enode server.jspara iniciar a aplicação. - Problema Avançado (Dificuldade: ⭐⭐): Construa uma imagem e execute um contêiner. Use
docker historypara analisar o tamanho de cada camada na imagem, identifique a maior camada e explique por quê. - Desafio (Dificuldade: ⭐⭐⭐): Crie um arquivo .dockerignore para excluir node_modules e .git, e compare a diferença no tamanho do contexto de build com e sem o arquivo .dockerignore (Dica: Procure a linha "Sending build context to Docker daemon" na saída do
docker build).