Python com Docker Compose e PostgreSQL Local
Aprenda a montar um ambiente local com Python, Docker Compose, PostgreSQL, FastAPI, variáveis de ambiente, migrações simples e testes reproduzíveis.
Um dos saltos mais importantes para quem estuda Python em 2026 é sair do projeto que só roda na própria máquina e chegar em um ambiente reproduzível. Vagas de backend, dados, automação e IA quase sempre citam Docker, PostgreSQL, variáveis de ambiente, testes e algum framework web. Mesmo quando a empresa não espera que uma pessoa júnior saiba Kubernetes, ela espera que o projeto abra sem drama: clonar, subir serviços, rodar testes e entender onde fica cada configuração.
Docker Compose resolve exatamente esse problema para projetos Python pequenos e médios. Em vez de instalar PostgreSQL manualmente, decorar porta, criar banco no clique e descobrir que cada colega usa uma versão diferente, você descreve os serviços em um arquivo compose.yaml. O resultado é um ambiente local mais próximo de produção, ótimo para portfólio, teste técnico, freelas e estudos.
Neste guia, vamos montar uma API Python simples com FastAPI, PostgreSQL, psycopg, variáveis de ambiente, script de inicialização e teste de conexão. A ideia não é criar uma arquitetura gigante. É mostrar o mínimo profissional: aplicação, banco, configuração, comandos claros e cuidados para não transformar Docker em caixa-preta.
Quando Docker Compose vale a pena
Use Docker Compose quando seu projeto depende de mais do que apenas Python puro. Alguns casos comuns:
| Projeto | Serviços típicos |
|---|---|
| API backend | aplicação Python, PostgreSQL, Redis |
| dashboard interno | app Python, banco, worker de atualização |
| automação de CRM | app, fila, banco de logs |
| RAG com documentos | API, PostgreSQL com pgvector, storage local |
| teste técnico | API, banco e dados de exemplo |
Para scripts curtos, venv ou uv pode bastar. Para uma API com banco, Docker Compose deixa o projeto mais fácil de revisar. A pessoa avaliadora não precisa instalar PostgreSQL na mão; ela só roda um comando e testa.
Se você ainda está no começo, leia também ambientes virtuais em Python, uv como gerenciador de pacotes e Python e Docker. O Compose não substitui essas bases; ele organiza dependências externas.
Estrutura do projeto
Vamos usar esta estrutura:
python-compose-postgres/
app/
__init__.py
main.py
settings.py
db.py
tests/
test_health.py
compose.yaml
Dockerfile
pyproject.toml
.env.example
README.md
A separação é simples de propósito. settings.py carrega configuração, db.py concentra conexão com o banco, main.py expõe a API e tests/ prova que o serviço responde. Em projetos reais, você pode adicionar models.py, repositories.py, services.py e migrações com Alembic, mas não comece com camadas vazias apenas para parecer enterprise.
Criando o projeto com uv
O fluxo abaixo usa uv, mas você pode adaptar para pip e venv:
uv init python-compose-postgres
cd python-compose-postgres
uv add fastapi uvicorn psycopg[binary] pydantic-settings
uv add --dev pytest httpx ruff
Um pyproject.toml mínimo pode ficar assim:
[project]
name = "python-compose-postgres"
version = "0.1.0"
description = "API Python com Docker Compose e PostgreSQL"
requires-python = ">=3.12"
dependencies = [
"fastapi",
"uvicorn[standard]",
"psycopg[binary]",
"pydantic-settings",
]
[dependency-groups]
dev = ["pytest", "httpx", "ruff"]
A escolha por pyproject.toml ajuda em vagas e portfólio porque mostra um projeto moderno. Se a empresa ainda usa requirements.txt, tudo bem; o importante é ter um comando claro e reprodutível.
Configuração com variáveis de ambiente
Nunca coloque senha real de banco no código. Mesmo em projeto de estudo, use .env.example para documentar o que precisa existir:
DATABASE_URL=postgresql://app:app@db:5432/app
APP_ENV=development
Agora crie app/settings.py:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
database_url: str
app_env: str = "development"
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
settings = Settings()
Esse padrão combina bem com o guia de pydantic-settings. O ganho prático é falhar cedo: se DATABASE_URL não existir, a aplicação avisa na inicialização em vez de quebrar no meio de uma requisição.
Dockerfile para a aplicação Python
O Dockerfile abaixo usa imagem oficial, instala dependências e roda o servidor:
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
COPY pyproject.toml ./
RUN pip install --no-cache-dir fastapi "uvicorn[standard]" "psycopg[binary]" pydantic-settings
COPY app ./app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Para um projeto de produção, você pode trocar o pip install direto por lockfile, usuário não root, multi-stage build e checagens de segurança. Para estudo e teste técnico, esse Dockerfile é compreensível e suficiente para mostrar o fluxo.
compose.yaml com PostgreSQL
Agora vem o coração do ambiente local:
services:
app:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://app:app@db:5432/app
APP_ENV: development
depends_on:
db:
condition: service_healthy
volumes:
- ./app:/app/app
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
O detalhe importante é o healthcheck. depends_on sozinho só garante que o container do banco foi iniciado, não que o PostgreSQL já aceita conexão. Com condition: service_healthy, a aplicação espera o banco ficar pronto antes de subir.
Também repare no host da URL: dentro da rede do Compose, a aplicação acessa o banco pelo nome do serviço, db, não por localhost. localhost dentro do container da aplicação aponta para o próprio container, não para o banco.
Conexão e rota de saúde
Crie app/db.py:
import psycopg
from app.settings import settings
def buscar_versao_postgres() -> str:
with psycopg.connect(settings.database_url) as conn:
with conn.cursor() as cur:
cur.execute("select version()")
row = cur.fetchone()
return row[0]
Agora app/main.py:
from fastapi import FastAPI
from app.db import buscar_versao_postgres
app = FastAPI(title="API Python com PostgreSQL")
@app.get("/health")
def health_check():
return {"status": "ok"}
@app.get("/db-version")
def db_version():
return {"postgres": buscar_versao_postgres()}
Suba o ambiente:
docker compose up --build
Em outro terminal, teste:
curl http://localhost:8000/health
curl http://localhost:8000/db-version
Se a segunda rota retornar a versão do PostgreSQL, a aplicação, a rede interna e a variável DATABASE_URL estão funcionando.
Inicializando tabelas sem complicar
Para um primeiro projeto, você pode criar uma função simples de inicialização:
import psycopg
from app.settings import settings
def criar_tabelas():
with psycopg.connect(settings.database_url) as conn:
with conn.cursor() as cur:
cur.execute(
"""
create table if not exists tarefas (
id serial primary key,
titulo text not null,
concluida boolean not null default false,
criada_em timestamptz not null default now()
)
"""
)
Em uma aplicação maior, use Alembic. O ponto para portfólio é mostrar que você sabe que schema do banco precisa ser versionado e reproduzido. Criar tabela manualmente no DBeaver e esquecer de documentar é um erro clássico em teste técnico.
Teste automatizado básico
Crie tests/test_health.py:
from fastapi.testclient import TestClient
from app.main import app
def test_health_check():
client = TestClient(app)
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
Rode localmente:
uv run pytest
Se quiser rodar testes dentro do container:
docker compose run --rm app pytest
Para que isso funcione, instale também as dependências de desenvolvimento na imagem ou crie um alvo específico para testes. Em projetos profissionais, é comum ter um Dockerfile mais completo para CI e uma imagem mais enxuta para produção.
Erros comuns
Usar localhost na DATABASE_URL dentro do container
Dentro do container, use db, não localhost:
# Errado dentro do Compose
postgresql://app:app@localhost:5432/app
# Certo
postgresql://app:app@db:5432/app
Subir sem healthcheck
Sem healthcheck, a API pode tentar conectar antes do PostgreSQL terminar de iniciar. Isso gera erro intermitente: às vezes funciona, às vezes quebra. Esse tipo de falha é ruim em teste técnico porque passa impressão de projeto frágil.
Commitar .env real
Use .env.example no repositório e coloque .env no .gitignore. Senhas, tokens e URLs reais nunca devem aparecer no Git. Mesmo que seja um banco local, o hábito importa.
Misturar migração, seed e aplicação
Criar tabela, inserir dados de exemplo e iniciar servidor são responsabilidades diferentes. Para estudo, uma função simples ajuda. Para produção, separe comandos: migrate, seed e start.
Não documentar comandos
Um bom README.md precisa dizer:
cp .env.example .env
docker compose up --build
curl http://localhost:8000/health
uv run pytest
Se a pessoa revisora precisa adivinhar o comando, seu projeto perde pontos mesmo que o código seja bom.
Como transformar isso em portfólio
Um projeto pequeno com Docker Compose pode ser mais forte do que um clone genérico de tutorial se resolver um problema reconhecível. Algumas ideias:
- API de candidaturas com status, empresa, tecnologia e link da vaga;
- controle de despesas com categorias e relatório mensal;
- catálogo de livros, cursos ou ferramentas Python;
- webhook de leads com deduplicação por e-mail;
- mini pipeline que importa CSV, valida dados e salva no PostgreSQL.
Conecte isso ao seu currículo Python para vaga júnior, ao guia de projetos de portfólio Python e à página de vagas Python. O diferencial não é apenas ter Docker no repositório; é conseguir explicar por que cada serviço existe, como rodar, como testar e o que faria diferente em produção.
Relação com outras stacks
Python é excelente para APIs, automações, dados e protótipos de produto. Em ambientes com alta concorrência, algumas equipes usam Python junto com Go ou Kotlin. Uma API administrativa pode ficar em Python enquanto serviços de baixa latência rodam em Go para backend. O importante para quem está aprendendo é entender os fundamentos compartilhados: container, banco, configuração, logs e testes.
Se você domina esse fluxo em Python, fica mais fácil conversar com equipes de DevOps, dados e produto. Docker Compose vira uma ponte entre o notebook que roda sozinho e o sistema que outras pessoas conseguem operar.
Checklist final
Antes de publicar seu projeto, confira:
compose.yamlsobe aplicação e banco;DATABASE_URLusa o host correto dentro da rede do Compose;.env.exampleexiste e.envnão está no Git;- há uma rota
/healthsimples; - o README tem comandos de instalação, execução e teste;
- pelo menos um teste automatizado passa;
- o projeto tem uma explicação de negócio, não apenas tecnologia;
- código, comentários e README usam português brasileiro claro.
Docker Compose não é obrigatório para todo script Python, mas é uma habilidade muito vendável. Ele mostra que você entende ambiente, dependência externa, banco, configuração e reprodutibilidade. Para quem quer trabalhar com backend, dados, automação ou IA aplicada, esse conjunto aparece em vagas reais e diferencia o portfólio de quem só empilhou arquivos soltos.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português