Fazendo deploy de FastAPI na Hetzner
Siga isso no seu próprio servidor. Comece grátis, sem cartão
uvicorn main:app --reload é como uma aplicação FastAPI roda num laptop; o que ela precisa pra produção é um gerenciador de processos de verdade, um banco de dados que sobrevive a um restart, migrations que rodam antes do código novo entrar no ar, e HTTPS na frente. Nada disso é específico do FastAPI exatamente, mas os detalhes diferem o suficiente de um deploy de Flask ou Django, um servidor ASGI em vez de WSGI, Alembic em vez das migrations embutidas do Django, que copiar um Dockerfile genérico de Python geralmente deixa alguma coisa pela metade. Este guia constrói a coisa de verdade em Docker num servidor Hetzner Cloud, com o Caddy cuidando dos certificados, e termina com uma aplicação FastAPI rodando no seu próprio domínio que você sabe como atualizar e avançar.
O que você vai precisar
- Um servidor Hetzner Cloud. Um CX23 (2 vCPU, 4 GB de memória) a €5,99/mês sem IVA roda uma única aplicação FastAPI com seu banco Postgres ao lado com conforto; suba pra um CX33 (4 vCPU, 8 GB, €8,99/mês) se a aplicação também faz trabalho pesado em segundo plano.
- Um domínio, com o DNS já apontado pro servidor antes de solicitar o HTTPS.
- Uma conta Hetzner Cloud e um token de API (Security → API Tokens, Read & Write) se você quiser provisionar o servidor pela API em vez do console.
- Uma aplicação FastAPI num repositório Git. Este guia usa o FastAPI 0.161.1, que exige Python 3.10 ou mais novo; o Dockerfile abaixo fixa o Python 3.13.
- Docker e o plugin do Compose, localmente pra testar o build primeiro ou direto no servidor.
Passo 1: criar o servidor
No Hetzner Cloud Console, crie um projeto se precisar, depois Add Server: escolha Ubuntu 24.04, o tipo de vCPU compartilhada CX23, uma localização (Nuremberg, Falkenstein e Helsinki todas ficam na UE), adicione sua chave SSH, e crie ele.
ssh root@your-server-ip
Você deve cair num shell root sem nenhum pedido de senha.
Passo 2: instalar o Docker
curl -fsSL https://get.docker.com | sh
docker --version
docker compose version
Passo 3: escrever o Dockerfile
FROM python:3.13-slim
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["fastapi", "run", "app/main.py", "--port", "8000"]
fastapi run é o comando de produção que os próprios docs do FastAPI apontam; ele roda o Uvicorn por debaixo sem um comando separado pra lembrar. libpq-dev e build-essential estão ali pro psycopg, que precisa compilar contra a biblioteca cliente do Postgres; remova-os se o seu requirements.txt usar psycopg[binary] ou asyncpg no lugar, já que os dois trazem wheels pré-compiladas.
Um requirements.txt mínimo pra este guia:
fastapi[standard]>=0.161
psycopg[binary]>=3.2
sqlalchemy>=2.0
alembic>=1.13
Construa uma vez pra confirmar que compila limpo:
docker build -t my-fastapi-app .
Passo 4: escrever o arquivo do Compose
services:
app:
build: .
restart: unless-stopped
env_file:
- .env
depends_on:
db:
condition: service_healthy
expose:
- "8000"
db:
image: postgres:18-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
timeout: 5s
retries: 5
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
depends_on:
- app
volumes:
db-data:
caddy-data:
caddy-config:
Seu arquivo .env no servidor:
DB_NAME=app
DB_USER=app
DB_PASSWORD=change-this
DATABASE_URL=postgresql+psycopg://app:change-this@db:5432/app
Valide antes de iniciar:
docker compose config
Passo 5: escrever o Caddyfile
api.example.com {
reverse_proxy app:8000
}
Passo 6: primeiro boot e migrations
docker compose up -d
docker compose exec app alembic upgrade head
docker compose ps
Você deve ver app, db e caddy todos running, e https://api.example.com deve responder com um certificado válido. Os docs interativos do FastAPI vivem em /docs por padrão; acessar isso deve mostrar os endpoints reais da sua API.
Passo 7: fazer deploy de novas versões
git pull
docker compose build app
docker compose up -d --no-deps app
docker compose exec app alembic upgrade head
Reconstruir só o app deixa db e caddy intocados, então o certificado e o banco de dados ficam no ar durante o deploy. Rodar a migration depois que o novo container já está de pé, em vez de como parte do build, significa que uma falha de migration aparece como um erro de comando claro em vez de uma imagem com deploy pela metade.
Backups
O Postgres no container db é a única peça de estado neste stack que não é reproduzível a partir do Git. Agende um dump diário pra fora do servidor:
docker compose exec -T db pg_dump -U app app | gzip > backup-$(date +%F).sql.gz
Copie esse arquivo pra armazenamento de objetos ou outra máquina num cron job, e restaure ele periodicamente num banco descartável pra confirmar que ele é de fato utilizável antes de você precisar dele de verdade.
O caminho mais curto: fazer isso com o Railyard
Deploy FastAPI to Hetzner roda a mesma receita a partir de uma URL de repositório, sem você escrever o Dockerfile, o arquivo do Compose ou o Caddyfile. Conecte um servidor com um token do Hetzner Cloud Console, ou traga um que você já provisionou. O Railyard detecta o FastAPI a partir do requirements.txt ou pyproject.toml, serve main:app na $PORT com o uvicorn (apontado pro seu próprio módulo automaticamente se a aplicação viver em outro lugar), e reconhece psycopg, asyncpg ou SQLAlchemy como uma dependência pra provisionar o Postgres com DATABASE_URL injetado. Defina alembic upgrade head como o comando de release em Settings → Deploy pra migrar em cada release, o mesmo que o Passo 6 acima mas rodado automaticamente antes de cada versão nova receber tráfego. Qualquer processo num Procfile se torna um worker que você pode escalar independentemente do processo web. Veja os docs de deploy from a repository pras regras exatas de detecção.
Resolução de problemas
connection refusedno primeiro boot: oappiniciou antes do Postgres terminar de inicializar.depends_oncom uma condição de healthcheck (como no Passo 4) deveria prevenir isso, mas se você remover o healthcheck, adicione um loop curto de retry em volta do primeiro comando de migration em vez de assumir que o banco está pronto no instante em que o container inicia.- Erros 500 sem nenhum detalhe na resposta: o FastAPI não expõe tracebacks nas respostas por padrão fora de ferramentas de debug; cheque
docker compose logs apppra ver a exceção real. ModuleNotFoundErrorpropsycopgem tempo de execução mesmo com um build limpo: geralmente uma incompatibilidade entrepsycopg(a variantepure Python) epsycopg[binary]oupsycopg2-binaryentre edições dorequirements.txt; fixe um explicitamente em vez de deixar um lockfile desatualizado resolver um diferente do pretendido.- O HTTPS nunca é emitido, o Caddy continua tentando de novo: o DNS ainda não está apontando pro servidor.
dig +short api.example.comdeve bater com o IP do servidor. - O Alembic diz “Target database is not up to date” no deploy: a migration de um deploy anterior não rodou, geralmente porque o comando de migration do Passo 7 foi pulado manualmente uma vez. Rode
docker compose exec app alembic currentpra ver onde o banco realmente está versusalembic headspra onde o código espera que ele esteja.
Perguntas frequentes
Por que fastapi run em vez de chamar o uvicorn direto? Os dois fazem a mesma coisa por debaixo; fastapi run é o comando que os próprios docs do FastAPI recomendam pra produção nas versões que este guia cobre, já que ele escolhe padrões sensatos sem flags extras.
Eu deveria usar múltiplos workers do Uvicorn num único container? Os próprios docs de deploy do FastAPI recomendam contra isso pra qualquer coisa rodando sob Docker Compose, Kubernetes, ou similar: deixe o orquestrador rodar múltiplas réplicas de container em vez de um container fazendo malabarismo com múltiplos processos worker, já que réplicas podem ser reiniciadas, escaladas e verificadas independentemente.
Postgres ou outra coisa? O SQLAlchemy e a maior parte do ecossistema FastAPI são agnósticos a banco de dados; este guia usa Postgres já que é a combinação mais comum, mas trocar a imagem do db e o esquema do DATABASE_URL funciona do mesmo jeito pro MySQL.
Um CX23 é realmente suficiente? Pra uma única API servindo volumes de requisição típicos, sim; 4 GB se dividem com conforto entre o container da aplicação e o Postgres. Trabalho pesado em CPU em segundo plano (processamento de dados grande, inferência de ML) é um motivo pra subir de tamanho.
E se minha aplicação usa uma app factory em vez de uma variável app no nível do módulo? Aponte o CMD do Dockerfile ou o comando detectado pelo Railyard explicitamente pra ela, por exemplo fastapi run app/main.py --port 8000 se torna a chamada equivalente contra um nome de função factory se for assim que sua aplicação está estruturada; o exemplo deste guia assume uma instância app simples.
Isso funciona com endpoints só-async e endpoints síncronos misturados numa aplicação? Sim; o FastAPI roda operações de rota síncronas num thread pool automaticamente pra que elas não bloqueiem o event loop, o que é parte do motivo pelo qual um único processo Uvicorn trata uma mistura dos dois sem configuração extra do seu lado.
Uma aplicação FastAPI num servidor Hetzner que você mesmo provisionou custa €5,99 por mês, com cada dependência, versão do Python e etapa de migration explícita em três arquivos que você controla. Lançar uma versão nova depois que eles existem é um rebuild de um container e um comando de migration, nada escondido no meio do caminho.
Por que escolhem o Railyard para isso
- Sua própria conta de cloud. Faça deploy no DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou em qualquer servidor Linux via SSH, cobrado direto pelo seu provedor, sem markup.
- Um preço fixo por equipe. O Free é grátis para sempre: um servidor mais um servidor de banco de dados, três apps, sem cartão. O Pro custa US$19 por mês para até cinco servidores e apps ilimitados. O Team custa US$49 por mês com servidores ilimitados. Sem cobrança por app, por deploy, por assento ou por gigabyte.
- Templates de um clique. Faça deploy de apps open source, como bancos de dados, analytics e ferramentas de dev, no seu próprio servidor em um clique.
- Bancos de dados gerenciados. Postgres, MySQL e Redis com backups programados, verificados por restauração, e recuperação point-in-time.
- Push-to-deploy. Faça deploy a partir do GitHub, GitLab ou da CLI, com HTTPS automático e releases sem downtime.