Python com Docker
LidoEsta aula ensina como containerizar aplicações Python com Docker, cobrindo desde a escolha da imagem base até boas práticas avançadas como multi-stage builds e otimização de camadas. Inclui exemplos práticos de Dockerfile, explicações detalhadas e exercícios com respostas.
Nesta aula, você aprenderá a criar imagens Docker para aplicações Python, um passo essencial para garantir consistência entre ambientes de desenvolvimento, teste e produção. Docker permite empacotar sua aplicação e todas as suas dependências em um contêiner leve e portátil, eliminando o clássico problema de "funciona na minha máquina".
Vamos explorar os principais elementos de um Dockerfile para Python, desde a escolha da imagem base até técnicas avançadas como multi-stage builds, além de boas práticas para criar imagens seguras, eficientes e fáceis de manter.
Imagem base
A imagem base é o alicerce do seu contêiner. Para aplicações Python, a escolha mais comum é a imagem oficial python disponível no Docker Hub. Existem várias variantes: python:3.12-slim (base Debian enxuta), python:3.12-alpine (base Alpine, ainda menor, mas com possíveis incompatibilidades), e python:3.12 (versão completa). A recomendação geral é usar a variante slim para reduzir o tamanho da imagem, mantendo compatibilidade com a maioria das bibliotecas.
Além disso, você pode usar imagens específicas para frameworks (ex.: python:3.12-slim-bullseye) ou imagens com pré-requisitos instalados, como python:3.12-slim já inclui o pip e o venv. Evite usar a tag latest para garantir reprodutibilidade; prefira versões fixas, como python:3.12.2-slim.
Exemplo de Dockerfile básico:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
requirements
O arquivo requirements.txt lista todas as dependências Python da aplicação. É crucial que ele seja copiado para a imagem antes da instalação, para que o Docker possa usar o cache de camadas (layer caching). Se você copiar o código antes de instalar as dependências, qualquer alteração no código invalidará o cache e forçará uma reinstalação completa, tornando o build mais lento.
Prática recomendada: primeiro copie o requirements.txt, instale as dependências e só depois copie o restante do código. Isso aproveita o cache do Docker: enquanto o requirements.txt não mudar, a camada de instalação não será reconstruída.
Exemplo:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Use --no-cache-dir no pip para evitar o cache de pacotes em disco, reduzindo o tamanho da imagem. Para produção, considere usar um arquivo de bloqueio como Pipfile.lock ou poetry.lock, mas requirements.txt é suficiente para a maioria dos projetos.
Multi-stage (visão geral)
Multi-stage builds permitem usar múltiplas imagens base em um único Dockerfile, separando o ambiente de build do ambiente de execução. Isso é útil para reduzir drasticamente o tamanho da imagem final, pois você pode compilar dependências em uma etapa e copiar apenas os artefatos necessários para a etapa final.
No contexto Python, é comum usar uma imagem completa (como python:3.12) para compilar pacotes que exigem compilador (por exemplo, psycopg2 ou numpy) e depois copiar os pacotes instalados para uma imagem slim. No entanto, para a maioria das aplicações Python puras, o multi-stage não é obrigatório, mas é uma boa prática para otimização.
Exemplo de multi-stage build para uma aplicação com dependências compiladas:
# Estágio 1: build
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --prefix=/install -r requirements.txt
# Estágio 2: runtime
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
CMD ["python", "app.py"]
Nesse exemplo, as dependências são instaladas no diretório /install na imagem builder, e depois copiadas para a imagem final. Isso evita que ferramentas de build (como gcc) fiquem na imagem final.
Boas práticas
Além dos pontos já mencionados, aqui estão outras boas práticas para Docker com Python:
- Use .dockerignore para excluir arquivos desnecessários (como
.git,__pycache__,.venv) do contexto de build, acelerando o envio e reduzindo o risco de vazamento de dados. - Execute como usuário não-root por segurança. Crie um usuário dedicado no Dockerfile e use
USERpara executar a aplicação. - Defina variáveis de ambiente com
ENVpara configurações comoPYTHONDONTWRITEBYTECODE=1(evita arquivos .pyc) ePYTHONUNBUFFERED=1(logs em tempo real). - Prefira
COPYem vez deADDquando não precisar de extração automática de arquivos. - Use
CMDem vez deENTRYPOINTpara comandos simples, a menos que precise de um contêiner executável. - Mantenha as camadas pequenas agrupando comandos relacionados, mas sem exagerar: cada RUN cria uma camada, e camadas são reutilizadas em cache.
Exemplo de Dockerfile com boas práticas:
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
RUN addgroup --system app && adduser --system --group app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app . .
USER app
EXPOSE 8000
CMD ["python", "app.py"]
Referências
- Dockerfile best practices - Documentação oficial Docker
- Imagem oficial Python no Docker Hub
- Ambientes virtuais Python (venv) - Documentação Python
- Pip - Instalador de pacotes Python
- Multi-stage builds - Documentação Docker
- Arquivo .dockerignore - Documentação Docker
- 12 Factor App - Boas práticas para aplicações modernas
Exercícios
- Exercício 1: Crie um Dockerfile simples para uma aplicação Flask que escuta na porta 5000. Use a imagem
python:3.12-slim, instale as dependências derequirements.txte defina o comando para rodarapp.py.✓ Resposta:FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 CMD ["python", "app.py"] - Exercício 2: Explique por que é importante copiar o
requirements.txtantes do código-fonte no Dockerfile e como isso afeta o cache de camadas.✓ Resposta: Copiar orequirements.txtantes do código permite que o Docker reutilize a camada de instalação das dependências enquanto o arquivo não mudar. Se o código for copiado primeiro, qualquer alteração no código invalida a camada e força a reinstalação de todas as dependências, tornando o build mais lento. Essa prática melhora a eficiência do processo de build. - Exercício 3: Escreva um Dockerfile multi-stage para uma aplicação que requer compilação de uma dependência (por exemplo,
psycopg2). Use a imagempython:3.12para build epython:3.12-slimpara runtime.✓ Resposta:# Estágio 1: build FROM python:3.12 AS builder WORKDIR /app COPY requirements.txt . RUN pip install --prefix=/install -r requirements.txt # Estágio 2: runtime FROM python:3.12-slim WORKDIR /app COPY --from=builder /install /usr/local COPY . . CMD ["python", "app.py"] - Exercício 4: Liste três boas práticas de segurança e três de performance em Dockerfiles Python.
✓ Resposta: Segurança: 1) Executar como usuário não-root; 2) Usar imagens oficiais e atualizadas; 3) Evitar expor portas desnecessárias. Performance: 1) Copiar requirements antes do código para aproveitar cache; 2) Usar imagens slim; 3) Combinar comandos RUN para reduzir camadas.
- Exercício 5: Crie um
.dockerignorepara um projeto Python que inclua os arquivos:.git,__pycache__,.venv,.enve*.pyc.✓ Resposta:.git __pycache__/ .venv .env *.pyc
Observações finais
Dominar Docker com Python é uma habilidade valiosa para o desenvolvimento moderno. Lembre-se de sempre testar suas imagens localmente e usar ferramentas como docker compose para orquestrar múltiplos serviços. À medida que seus projetos crescem, revise os Dockerfiles periodicamente para incorporar novas boas práticas e melhorar a eficiência.