O problema
Até agora você usou imagens prontas: alpine:3.23, nginx:alpine. Mas container só é útil quando você empacota sua aplicação.
O Dockerfile é a receita. Um arquivo de texto que descreve como construir uma imagem: sistema base, dependências, código, configuração. Com docker build, ele vira uma imagem que executa igual em qualquer lugar.
A estrutura de um Dockerfile
Cada instrução gera uma camada (layer). As camadas são readonly e empilhadas via overlay. Isso significa que:
- Camadas são cacheadas: se você mudar só a linha 10, as primeiras 9 não são reconstruídas
- Quanto mais cedo uma camada muda, mais camadas são reconstruídas depois dela
- A ordem importa: dependências primeiro, código depois
Exemplo mínimo: app Python
FROM python:3.13-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]O que cada linha faz:
FROMdefine a imagem base (Python 3.13 em Alpine)WORKDIRcria e entra no diretório/app- O primeiro
COPYtrazrequirements.txt. Só ele. Se as dependências não mudaram, essa camada vem do cache RUN pip installinstala as dependências. Camada cacheável porquerequirements.txtraramente muda- O segundo
COPY . .traz o código da aplicação. Essa camada muda a cada deploy EXPOSE 8000documenta a porta (não publica; isso é nodocker run -p)CMDdefine o comando padrão quando o container iniciar
Se você mudar app.py, só a camada do COPY . . é reconstruída. As camadas 1-4 vêm do cache. Se você colocasse COPY . . antes do pip install, toda mudança de código rebaixaria as dependências e o cache seria inútil.
Instruções principais
- FROM: imagem base.
FROM python:3.13-alpine,FROM node:22-alpine - RUN: executa comando durante o build.
RUN apt update && apt install -y curl - COPY: copia arquivo do host pra imagem.
COPY . /app - WORKDIR: define o diretório de trabalho. Equivale a
cd /app - EXPOSE: documenta porta (não publica; isso é no
docker run -p) - CMD: comando padrão quando o container iniciar
- ENTRYPOINT: similar a CMD mas mais rígido (não é sobrescrito por argumentos do
docker run) - ENV: variáveis de ambiente.
ENV NODE_ENV=production
Multi-stage builds
Problema: você precisa de compilador pra buildar, mas não quer ele na imagem final (imagem maior, superfície de ataque). Solução: múltiplos FROM no mesmo Dockerfile.
Stage 1 compila o binário:
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o server .Stage 2 é a imagem final, sem Go, sem toolchain:
FROM alpine:3.23
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]A imagem final tem só o binário compilado + Alpine. De 900 MB pra 10 MB.
Boas práticas
- Dependências antes do código: maximiza cache de layers
- Multi-stage: separa build de runtime
.dockerignore: evita copiarnode_modules,.git,build/- Imagens alpine/slim: reduz tamanho e superfície de ataque
- Tags específicas:
python:3.13-alpine, nãopython:latest - Um processo por container: não bota nginx + redis + app no mesmo container. Use Compose