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



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
# 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.

100%
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

TEXT 📖 Somente leitura
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
# ============================================
# 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

TEXT 📖 Somente leitura
# .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: ⭐⭐)

BASH
# 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: ⭐⭐)

BASH
# 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: ⭐⭐)

BASH
# 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: ⭐⭐)

BASH
# 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: ⭐⭐⭐)

BASH
# ============================================
# 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

BASH
# 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

BASH
# ============================================
# 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 latest sempre 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 de latest.

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 requer RUN apk add --no-cache tzdata para suportar a variável TZ.


📖 Resumo


📝 Exercícios

  1. Tarefa Básica (Dificuldade: ⭐): Clone um projeto Flask/Express de código aberto, escreva um Dockerfile para construir uma imagem e execute-o.
  2. 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.
  3. Desafio (Dificuldade: ⭐⭐⭐): Use dive para analisar a imagem otimizada, identifique camadas que podem ser ainda mais otimizadas, corrija-as e verifique a redução no tamanho do arquivo.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%