Docker: Prática Integrada da Fase 2
Última atualização: 2026-08-26
Unindo os Conceitos da Fase 2 — Do Código Fonte à Imagem Pronta para Produção: Containerizando Completamente uma Aplicação Web.
1. O Que Você Vai Aprender
- Um Projeto Prático Completo Construído em Múltiplas Fases
- Política de Gerenciamento de Configuração de Ambiente
- Soluções de Persistência de Logs
- Verificações de Saúde e Configuração de Auto-recuperação
- Tags de Imagem e Controle de Versão
2. Uma História Sobre Assumir um Projeto Legado
(1) Ponto de Dor: Projetos legados que não rodam no Docker
Charlie assumiu um projeto Python legado que "não rodava no Docker" — tinha um Dockerfile de 1,2 GB, levava 10 minutos para construir a cada vez, não tinha verificações de saúde, escrevia logs dentro do contêiner (que eram perdidos na reinicialização), executava como usuário root e tinha variáveis de ambiente fixas no código. A implantação levava 30 minutos e era instável.
(2) Uma Solução Completamente Containerizada
Alice o ajudou analisando a árvore de dependências, escrevendo um Dockerfile multi-estágio, configurando a persistência de logs, adicionando verificações de saúde e executando a aplicação como usuário não-root. No final, o projeto era construído e implantado em menos de 3 minutos.
# Dockerfile de produção multi-estágio
FROM python:3.12-slim AS builder
# ... construir venv otimizado
FROM python:3.12-slim
# ... copiar venv, não-root, healthcheck
(3) Duração: De 30 minutos para 3 minutos
| Métrica | Antes da Otimização | Após a Otimização |
|---|---|---|
| Tamanho da Imagem | 1,2 GB | 150 MB |
| Tempo de Build | 10 minutos | 3 minutos |
| Tempo de Implantação | 30 minutos | 3 minutos |
| Pontuação de Segurança | 45 | 85 |
3. Design da Arquitetura do Projeto
(1) Arquitetura da Aplicação
Nesta aula, vamos containerizar uma aplicação Flask de Lista de Tarefas: Flask + PostgreSQL + Nginx.
graph TB
USER["Navegador"] --> NGX["Nginx<br/>Proxy Reverso + Estático"]
NGX -->|"proxy_pass :5000"| APP["Aplicação Flask<br/>API + Interface de Lista de Tarefas"]
APP -->|"psycopg2 :5432"| DB["PostgreSQL<br/>Banco de Dados de Tarefas"]
DB --> VOL["Volume Nomeado<br/>pg-data"]
APP --> LOGS["Volume Nomeado<br/>app-logs"]
(2) Estratégia de Containerização
| Componente | Imagem Base | Política |
|---|---|---|
| Aplicação Flask | python:3.12-slim |
Build Multi-estágio + Não-root + HEALTHCHECK |
| PostgreSQL | postgres:15-alpine |
Imagem oficial + Volume Nomeado |
| Nginx | nginx:1.25-alpine |
Imagem Oficial + Bind Mount de Arquivo de Configuração |
4. Dockerfile para uma Aplicação Flask
(1) Estrutura do Projeto
todo-app/
├── app.py # Aplicação Flask
├── requirements.txt # Dependências Python
├── templates/ # Templates HTML
│ └── index.html
├── Dockerfile # Build de produção multi-estágio
├── .dockerignore # Excluir arquivos desnecessários
├── nginx.conf # Configuração de proxy reverso Nginx
└── docker-run.sh # Script de implantação com um clique
▶ Exemplo: Escrevendo um Dockerfile Multi-estágio (Dificuldade: ⭐⭐⭐)
# ============================================
# Dockerfile multi-estágio para App Flask de Tarefas
# Estágio 1: Construir ambiente virtual
# Estágio 2: Runtime de produção
# ============================================
# ---------- Estágio 1: Builder ----------
FROM python:3.12-slim AS builder
WORKDIR /build
# Cache: instalar dependências primeiro
COPY requirements.txt .
RUN python -m venv /opt/venv && \
/opt/venv/bin/pip install --no-cache-dir -r requirements.txt
# ---------- Estágio 2: Produção ----------
FROM python:3.12-slim
# Instalar curl para healthcheck
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# Criar usuário não-root
RUN groupadd -r appgroup && \
useradd -r -g appgroup -u 1000 -m -d /home/appuser appuser
WORKDIR /app
# Copiar ambiente virtual do builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copiar código da aplicação
COPY --chown=appuser:appgroup . .
# Criar diretório de logs
RUN mkdir -p /app/logs && chown appuser:appgroup /app/logs
# Alternar para usuário não-root
USER appuser
# Variáveis de ambiente (padrões, sobrescrever em tempo de execução)
ENV FLASK_ENV=production \
LOG_LEVEL=info \
APP_PORT=5000
EXPOSE 5000
# Verificação de saúde
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# Iniciar a aplicação
CMD ["python", "app.py"]
(2) .dockerignore
# .dockerignore
__pycache__
*.pyc
*.pyo
.git
.env
venv
.venv
*.md
Dockerfile
docker-compose*.yml
5. Gerenciamento de Configuração de Ambiente
(1) Hierarquia de Variáveis de Ambiente
| Nível | Método | Prioridade | Cenários Aplicáveis |
|---|---|---|---|
| Dockerfile ENV | Valor Padrão | Mínima (1) | Configuração Padrão Geral |
| docker run -e | Sobrescrita em Tempo de Execução | Média (2) | Configuração de Implantação |
| Arquivo .env | --env-file |
Parte 3 | Gerenciando Múltiplas Variáveis |
| Docker Secrets | --secret |
Máxima | Informações Sensíveis |
▶ Exemplo: Configurando Variáveis de Ambiente (Dificuldade: ⭐⭐)
# Executar com variáveis de ambiente personalizadas
docker run -d \
--name todo-app \
-e FLASK_ENV=production \
-e DATABASE_URL="postgresql://appuser:apppass@todo-db:5432/tododb" \
-e LOG_LEVEL=warning \
-p 5000:5000 \
todo-app:1.0
▶ Exemplo: Usando um arquivo .env (Dificuldade: ⭐⭐)
# Criar arquivo .env
cat > .env << 'EOF'
FLASK_ENV=production
DATABASE_URL=postgresql://appuser:apppass@todo-db:5432/tododb
LOG_LEVEL=warning
APP_PORT=5000
EOF
# Executar com --env-file
docker run -d \
--name todo-app \
--env-file .env \
-p 5000:5000 \
todo-app:1.0
6. Persistência de Logs
▶ Exemplo: Configurando a Persistência de Logs (Dificuldade: ⭐⭐)
# Montar um volume nomeado para logs da aplicação
docker run -d \
--name todo-app \
-v app-logs:/app/logs \
-e LOG_LEVEL=info \
todo-app:1.0
(1) Comparação de Políticas de Logging
| Solução | Vantagens | Desvantagens |
|---|---|---|
| Logging dentro do contêiner | Simples | Perdido quando o contêiner é excluído |
| Persistência com Volume | Persiste após a exclusão do contêiner | Rotação de logs deve ser gerenciada manualmente |
| stdout + driver de log | Gerenciamento nativo do Docker | Configuração complexa |
| Logging Centralizado (ELK/Loki) | Pesquisabilidade full-stack | Requer infraestrutura adicional |
7. Tags de Imagem e Gerenciamento de Versão
(1) Estratégia de Tag
| Estratégia | Formato da Tag | Exemplo | Cenários Aplicáveis |
|---|---|---|---|
| Versionamento Semântico | vMaior.Menor.Patch |
v2.1.0 |
Lançamento de Produção |
| Git SHA | sha-<commit> |
sha-a1b2c3d |
Rastreamento CI/CD |
| latest | latest |
latest |
Desenvolvimento/Início Rápido |
| Nome do Branch | <branch> |
main, dev |
Ambiente de Desenvolvimento |
▶ Exemplo: Push com Múltiplas Tags (Dificuldade: ⭐⭐)
# Construir com múltiplas tags
docker build -t todo-app:v1.0.0 -t todo-app:latest .
# Enviar todas as tags
docker push localhost:5000/todo-app:v1.0.0
docker push localhost:5000/todo-app:latest
8. Implantando a Stack Completa
▶ Exemplo: Implantação com Um Clique de Flask + PostgreSQL + Nginx (Dificuldade: ⭐⭐⭐)
# ============================================
# Script de implantação com um clique
# ============================================
# 1. Criar rede e volumes
docker network create todo-net
docker volume create pg-data
docker volume create app-logs
# 2. Iniciar PostgreSQL
docker run -d \
--name todo-db \
--network todo-net \
-e POSTGRES_USER=appuser \
-e POSTGRES_PASSWORD=apppass \
-e POSTGRES_DB=tododb \
-v pg-data:/var/lib/postgresql/data \
--restart=unless-stopped \
postgres:15-alpine
# 3. Aguardar inicialização do PostgreSQL
sleep 15
# 4. Construir e iniciar aplicação Flask
docker build -t todo-app:1.0 ./todo-app
docker run -d \
--name todo-app \
--network todo-net \
-e DATABASE_URL="postgresql://appuser:apppass@todo-db:5432/tododb" \
-v app-logs:/app/logs \
--restart=unless-stopped \
todo-app:1.0
# 5. Iniciar proxy reverso Nginx
docker run -d \
--name todo-nginx \
--network todo-net \
-p 80:80 \
-v $(pwd)/todo-app/nginx.conf:/etc/nginx/conf.d/default.conf \
--restart=unless-stopped \
nginx:1.25-alpine
# 6. Verificar
docker ps
curl http://localhost/health
9. Resultados de Validação e Otimização
(1) Validar a otimização usando dive
# Analisar a imagem multi-estágio
dive todo-app:1.0
# Esperado: pontuação de eficiência > 90%, sem espaço desperdiçado
(2) Comparação de Tamanhos de Imagem
| versão | Dockerfile | Tamanho da imagem | Tempo de build |
|---|---|---|---|
| V1 Estágio Único | FROM python:3.12 + COPY . . + RUN pip install |
1,2 GB | 5 minutos |
| V2 Imagem Base Otimizada | FROM python:3.12-slim + Limpeza de Cache |
350 MB | 3 minutos |
| V3 Multi-estágio | builder + slim + não-root + HEALTHCHECK | 150 MB | 3 minutos (30 segundos com cache atingido) |
10. Exemplo Completo: Containerizando uma Aplicação de Lista de Tarefas
# ============================================
# Passo a passo completo: Containerizar uma app Flask
# Abrange: Dockerfile, build, implantação multi-contêiner
# ============================================
# 1. Construir a imagem otimizada
docker build -t todo-app:v1.0.0 -t todo-app:latest ./todo-app
# 2. Verificar a imagem
docker images todo-app
docker history todo-app:v1.0.0
# 3. Criar infraestrutura
docker network create todo-net
docker volume create pg-data
docker volume create app-logs
# 4. Iniciar PostgreSQL com armazenamento persistente
docker run -d \
--name todo-db \
--network todo-net \
-e POSTGRES_USER=appuser \
-e POSTGRES_PASSWORD=apppass \
-e POSTGRES_DB=tododb \
-v pg-data:/var/lib/postgresql/data \
--restart=unless-stopped \
postgres:15-alpine
# 5. Iniciar aplicação Flask
docker run -d \
--name todo-app \
--network todo-net \
-e DATABASE_URL="postgresql://appuser:apppass@todo-db:5432/tododb" \
-v app-logs:/app/logs \
--restart=unless-stopped \
todo-app:v1.0.0
# 6. Iniciar proxy reverso Nginx
docker run -d \
--name todo-nginx \
--network todo-net \
-p 8080:80 \
-v $(pwd)/todo-app/nginx.conf:/etc/nginx/conf.d/default.conf \
--restart=unless-stopped \
nginx:1.25-alpine
# 7. Verificar stack completa
echo "=== Contêineres ===" && docker ps
echo "=== Verificação de Saúde ===" && docker inspect todo-app --format='{{.State.Health.Status}}'
echo "=== Resposta da App ===" && curl -s http://localhost:8080/health
# 8. Enviar para registry privado (opcional)
docker tag todo-app:v1.0.0 localhost:5000/todo-app:v1.0.0
docker push localhost:5000/todo-app:v1.0.0
# 9. Limpar (opcional)
# docker stop todo-nginx todo-app todo-db
# docker rm todo-nginx todo-app todo-db
❓ Perguntas Frequentes
P: Um contêiner executa um processo? R: A melhor prática é "um contêiner, um processo" — um contêiner para a aplicação Flask, um para o Nginx e um para o PostgreSQL. Benefícios: escalabilidade independente, atualizações independentes e isolamento de falhas. Contraexemplo: Se um contêiner executar Nginx, Flask e PostgreSQL, uma falha em qualquer componente derruba todo o sistema, e a escalabilidade independente não é possível.
P: Os arquivos de log devem ser escritos dentro do contêiner ou em um volume montado? R: Uma combinação de ambas as abordagens: ① A aplicação escreve tanto no stdout (que pode ser visualizado com
docker logs) quanto em um arquivo (persistido em um volume montado); ② Em um ambiente de produção, recomendamos usar stdout combinado com coleta centralizada de logs (ELK/Loki), e não mais escrever em arquivos. A abordagem baseada em volume abordada nesta lição é uma solução intermediária.
P: Como distinguir entre ambientes de desenvolvimento e produção durante o processo de build? R: Existem três métodos: ① ARG + RUN condicional (não recomendado, pois torna o Dockerfile mais complexo); ② Múltiplos Dockerfiles (Dockerfile.dev / Dockerfile.prod); ③ Builds multi-estágio + seleção --target (recomendado). Nesta lição, usaremos variáveis ENV para definir valores padrão e sobrescrevê-los com
docker run -e.
P: Como os números de versão das imagens são gerenciados? R: Versionamento Semântico (SemVer): versão principal.versão secundária.número de build (ex.: v2.1.0). No CI/CD, tags Git SHA (ex.: sha-a1b2c3d) também são aplicadas para facilitar a rastreabilidade. A tag
latestsempre aponta para a versão estável mais recente, mas em ambientes de produção, use o número de versão exato em vez delatest.
P: Como definir o fuso horário de um contêiner? R: Existem duas maneiras: ①
docker run -e TZ=Asia/Shanghai(requer instalar tzdata na imagem base); ②-v /etc/localtime:/etc/localtime:ro(usa o fuso horário do host diretamente). A imagem Alpine requerRUN apk add --no-cache tzdatapara suportar a variável TZ.
📖 Resumo
- Build multi-estágio: O venv é criado durante o estágio Builder, e o estágio Runner apenas copia o venv; o tamanho da imagem é reduzido de 1,2 GB para 150 MB
- Gerenciamento hierárquico de variáveis de ambiente: Dockerfile ENV (padrão) →
docker run -e(sobrescrita em tempo de execução) → arquivo .env (múltiplas variáveis) - Persistência de Logs: Um Volume Nomeado é montado em /app/logs; os logs são retidos após o contêiner ser reiniciado ou reconstruído
- Verificações de saúde: curl /health endpoint; o Docker detecta automaticamente problemas e implementa uma política de reinicialização para auto-recuperação
- Gerenciamento de versão de imagens: versionamento semântico + Git SHA + push com múltiplas tags "latest"
- Executar como não-root + HEALTHCHECK + persistência de logs = Os três elementos essenciais de um Dockerfile de nível de produção
📝 Exercícios
- Tarefa Básica (Dificuldade: ⭐): Clone um projeto Flask/Express de código aberto, escreva um Dockerfile para construir uma imagem e execute-o.
- Exercício Avançado (Dificuldade: ⭐⭐): Escreva um Dockerfile multi-estágio para este projeto e compare as diferenças de tamanho de imagem entre imagens de estágio único e multi-estágio.
- Desafio (Dificuldade: ⭐⭐⭐): Use
divepara analisar a imagem otimizada, identifique camadas que podem ser ainda mais otimizadas, corrija-as e verifique a redução no tamanho do arquivo.