Como colocar uma API FastAPI em uma VPS com Docker e HTTPS

Ilustração de um servidor com container e cadeado representando o deploy de uma API com Docker e HTTPS.

Para colocar uma API FastAPI numa VPS com HTTPS, vamos executar a aplicação em um container e usar o Caddy para receber as conexões do domínio. O Docker Compose organiza os serviços, o reinício e a verificação de saúde.

O projeto abaixo tem duas rotas e uma variável de ambiente. A API fica na rede privada do Compose; somente o proxy publica as portas web.

A abordagem segue a documentação oficial do FastAPI sobre Docker, que aponta a imagem de container Linux, normalmente montada com Docker, como o jeito comum de colocar uma API FastAPI em produção.

O que você precisa antes de começar

Antes de abrir o terminal, confere se tu tem tudo isso em mãos:

  • Uma VPS Linux com acesso root: lembrando que VPS é servidor autogerenciado, então atualizações, firewall e manutenção ficam por sua conta
  • Docker e Docker Compose v2 instalados: os comandos deste guia usam a sintaxe docker compose
  • Um domínio próprio com acesso ao painel de DNS
  • Portas 80 e 443 liberadas externamente: é requisito do Caddy para o HTTPS automático, junto com o registro DNS apontando para o servidor e um diretório de dados gravável e persistente
  • Noções de terminal via SSH: nada avançado, só conectar no servidor e editar arquivos

Qual VPS usar para FastAPI com Docker?

Pra este projeto, a recomendação principal da Hora de Codar é a VPS da Hostinger (aviso: este artigo contém links de afiliado da Hostinger).

O motivo é técnico, ligado ao que este tutorial pede:

  • Acesso root completo para instalar e administrar o Docker
  • Processadores AMD EPYC e armazenamento SSD NVMe
  • Opção de servidor no Brasil, para escolher uma região próxima do público da API
  • Backups semanais grátis e snapshots manuais, para complementar o plano de recuperação antes de atualizações
  • Gerenciador de firewall, útil no passo de liberar as portas 80 e 443
  • Template com Docker: Ubuntu 24.04 com docker-ce e docker-compose já instalados

O template é uma opção para uma VPS nova. Reinstalar o sistema operacional de um servidor em uso pode apagar dados; confira o sistema selecionado e os backups antes de qualquer troca.

Depois, conectado via SSH, confira a instalação:

docker --version
docker compose version

Este tutorial exige o plugin Compose v2, chamado com docker compose (com espaço). Se esse comando não existir, instale o plugin conforme a documentação do Docker para sua distribuição antes de continuar.

Confirme os dois comandos antes de criar os arquivos.

Sobre o plano: pra uma API mínima como a deste guia, o KVM 1 (1 vCPU, 4 GB de RAM e 50 GB NVMe) pode ser um ponto de partida para avaliação.

O consumo real da aplicação determina quando aumentar os recursos do plano.

Passo a passo: FastAPI na VPS com Docker e HTTPS

A estrutura de pastas do projeto fica assim:

minha-api/
├── app/
│   └── main.py
├── conf/
│   └── Caddyfile
├── compose.yaml
├── Dockerfile
└── requirements.txt

Crie a pasta minha-api, com os diretórios app e conf, e mantenha os arquivos nos caminhos indicados.

Passo 1: criando a API mínima

Crie o arquivo app/main.py com duas rotas: a /, que responde a API em si, e a /health, que vai servir pro healthcheck do Docker.

O código abaixo é um exemplo didático:

import os

from fastapi import FastAPI

app = FastAPI()

APP_NAME = os.getenv("APP_NAME", "minha-api")


@app.get("/")
def read_root():
    return {"app": APP_NAME, "status": "ok"}


@app.get("/health")
def health():
    return {"status": "healthy"}

A variável APP_NAME será configurada no Compose. Não coloque segredos reais nesse exemplo.

O Dockerfile do passo 3 roda app/main.py, então se o arquivo estiver na raiz o container não acha a aplicação.

Passo 2: definir as dependências

Crie o arquivo requirements.txt na raiz do projeto. Para este exemplo, vamos usar estas versões:

fastapi[standard]==0.143.2
pydantic==2.14.1

Mantenha as versões escolhidas no controle de versão e avalie as atualizações em um ambiente separado antes de aplicar na VPS.

Essas duas linhas fixam as dependências diretas. Para reproduzir também as dependências transitivas, gere e versione um arquivo de lock com a ferramenta usada pelo projeto.

Passo 3: escrevendo o Dockerfile

Este é o Dockerfile do exemplo, com o comando preparado para receber tráfego do proxy:

FROM python:3.14

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers", "--forwarded-allow-ips", "*"]

E por que essa ordem?

O requirements.txt é copiado e instalado ANTES do código porque muda pouco.

Assim o Docker reaproveita o cache dessa camada e não reinstala tudo a cada alteração no main.py.

O CMD em formato de lista executa o comando do servidor diretamente.

E o --proxy-headers?

A documentação do FastAPI cita Nginx e Traefik como exemplos de proxy na frente do container, e aqui quem faz esse papel é o Caddy.

Ele recebe a conexão HTTPS e repassa pra API por dentro da rede do Docker.

O --proxy-headers habilita a interpretação desses cabeçalhos, e --forwarded-allow-ips define de quais conexões eles serão aceitos. O uso de * neste exemplo pressupõe que apenas o Caddy, na rede da stack, acessa a API. Não publique uma porta direta da API nem conecte containers não confiáveis a essa rede. A documentação sobre proxy no FastAPI explica essa relação.

Passo 4: separando desenvolvimento e produção

No ambiente local, publique uma porta apenas em loopback para acessar a API diretamente.

Um compose.yaml só pra desenvolvimento fica assim:

services:
  api:
    build: .
    ports:
      - "127.0.0.1:8000:80"

Com ele, rode:

docker compose up --build -d

E abra http://localhost:8000/ e http://localhost:8000/health no navegador pra ver o JSON.

Se a porta 8000 estiver ocupada, altere esse número no mapeamento local. O 80 final continua sendo a porta interna da API.

Em produção a história muda: a API NÃO tem ports.

A porta 80 dela fica só dentro da rede do Compose, e quem publica 80 e 443 para o mundo é o Caddy.

No servidor, esse arquivo de desenvolvimento é substituído pela versão do passo 5.

Passo 5: montando o compose.yaml de produção

No servidor, o compose.yaml completo junta a API e o Caddy:

services:
  api:
    build: .
    restart: unless-stopped
    environment:
      APP_NAME: "minha-api"
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:80/health')"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./conf:/etc/caddy
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

Pontos da configuração:

  • restart: unless-stopped: conforme a referência de serviços do Compose, o container é reiniciado seja qual for o código de saída, mas deixa de reiniciar quando o serviço é parado ou removido
  • healthcheck: ajusta comando, intervalo e tentativas da verificação, conforme a referência de serviços do Compose. Os tempos mostrados são valores de exemplo.
  • Comando do healthcheck: o exemplo usa Python com urllib, já presente na imagem, sem depender de curl. Se a verificação falhar até atingir o limite de tentativas, o estado de saúde passa a unhealthy; investigue a aplicação pelos logs
  • environment: o APP_NAME é um valor fictício. Para segredos, use o mecanismo apropriado do seu ambiente e mantenha os valores fora do repositório.
  • Volumes do Caddy: a imagem oficial do Caddy tem os pontos de montagem /data e /config, e o Caddyfile vai em /etc/caddy (por isso o ./conf:/etc/caddy)

O /data guarda os certificados TLS e as chaves privadas, por isso ele é um volume nomeado.

Se quiser aprofundar nesse assunto de volumes, imagens e organização, tem um guia de boas práticas Docker na VPS aqui no blog.

Perder esse volume remove os certificados e chaves guardados pela stack.

Passo 6: configurando o Caddyfile

Crie o arquivo conf/Caddyfile com o seu domínio:

api.seudominio.com.br {
    reverse_proxy api:80
}

O api ali é o NOME do serviço no Compose, e o 80 é a porta interna da API.

O domínio precisa aparecer nesse arquivo: é a partir dele que o Caddy sabe para qual nome emitir o certificado.

Dentro do container do Caddy, localhost é o próprio Caddy, não a sua API.

Passo 7: apontando o DNS para a VPS

No painel do seu provedor de DNS, crie um registro do tipo A para o subdomínio (no exemplo, api) apontando para o IP da VPS.

Só crie um registro AAAA se o IPv6 da VPS estiver configurado e acessível. Um AAAA antigo apontando para outra máquina também pode atrapalhar a validação do certificado.

E o domínio tem que ser exatamente o mesmo que está no Caddyfile.

Se o DNS ainda apontar para outro servidor, a validação do certificado pode falhar. Confirme o registro antes de subir os serviços.

Espere o registro resolver para o IP certo antes do passo 9.

Passo 8: liberando as portas no firewall

Libere as portas 80 e 443 externamente.

Na Hostinger, o painel da VPS tem um gerenciador de firewall onde você cria essas regras.

A porta da API não precisa e não deve ser liberada: ela só conversa com o Caddy por dentro da rede do Docker.

A porta 80 também é requisito do HTTPS automático do Caddy e é por ela que o redirecionamento de HTTP para HTTPS acontece.

Passo 9: subindo tudo

Na pasta do projeto, no servidor, rode:

docker compose up --build -d

Esse comando monta a imagem da API e sobe os serviços em segundo plano.

Depois acompanhe os logs em tempo real:

docker compose logs -f

Confira os dois serviços:

Do lado do Caddy, mensagens sobre a obtenção do certificado do seu domínio, e qualquer erro de validação (geralmente DNS ou porta fechada).

Do lado da API, a inicialização do servidor e as requisições chegando, inclusive as do healthcheck.

Como verificar se a API está no ar com HTTPS

A checagem final é pública, de fora do servidor:

  1. Abra https://api.seudominio.com.br/ no navegador: deve aparecer o JSON da rota /, com o app e o status
  2. Abra https://api.seudominio.com.br/health: deve aparecer o JSON do healthcheck
  3. Confira o cadeado no navegador e se o certificado foi emitido para o seu domínio
  4. Acesse a versão com http://: o Caddy redireciona automaticamente para https://
  5. Volte no docker compose logs -f e veja se a API está respondendo às chamadas do healthcheck sem erro

Se essas verificações passarem, o endereço público está respondendo pelo proxy com HTTPS.

O certificado é emitido para o nome configurado no Caddyfile, então pelo IP o HTTPS não vai bater.

Manutenção: logs, reinício e atualização da imagem

Mudou o código ou o requirements.txt? Rode de novo:

docker compose up --build -d

O Compose remonta a imagem da API com as mudanças e recria o container.

E graças à ordem do Dockerfile, se só o main.py mudou, a instalação das dependências vem do cache.

Para atualizar apenas a imagem do Caddy e recriar seu container:

docker compose pull caddy
docker compose up -d caddy

A política restart: unless-stopped trata o reinício dos containers. Confirme também que o serviço Docker está configurado para iniciar com o sistema operacional; essa é uma configuração da VPS.

Uma parada manual muda esse comportamento. Confira o estado dos serviços e os logs depois de reiniciar a VPS.

Persistência: neste projeto só o Caddy precisa de volume (/data e /config).

A API é stateless, não guarda nada em disco, então pode ser recriada à vontade.

E o certificado? O Caddy renova automaticamente os certificados que gerencia, tu não precisa agendar nada.

Pra investigar qualquer problema, o caminho é sempre o mesmo: docker compose logs -f.

Apagar os volumes caddy_data e caddy_config remove o material TLS persistido e exige nova emissão de certificados.

Antes de mudanças grandes (trocar versão do FastAPI, mexer no sistema), vale tirar um snapshot manual no painel da Hostinger, além dos backups semanais que já existem.

Confirme a política de retenção e teste a recuperação num ambiente separado. O snapshot complementa os backups da aplicação.

Mais um projeto em VPS no canal

Se você curte esse tipo de setup, no canal tem um vídeo mostrando como configurar a Evolution API e o n8n em uma VPS, pra montar automações com WhatsApp.

Vídeo complementar sobre Evolution API e n8n em VPS. O exemplo deste artigo usa FastAPI.

Próximo passo

Agora é trocar o main.py de exemplo pela sua API real, mantendo duas coisas: a versão fixada no requirements.txt e a rota /health ligada ao healthcheck.

Banco de dados entra só quando o projeto pedir, e aí sim você planeja volume e backup pra ele.

Antes de atualizar, revise as mudanças, a exposição das portas e o caminho de recuperação.

Se ainda não tem servidor, dá pra conferir a VPS para este projeto e começar pelo template com Docker.

E deixa a documentação oficial do FastAPI sobre deploy nos favoritos, ela é a referência quando bater dúvida.

Quer aprender mais sobre inteligência artificial e programação na prática? Conheça o canal Hora de Codar no YouTube e acompanhe os próximos vídeos.

Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Claude Code

Formação Claude Code

Domine Claude Code do absoluto zero até o avançado

  • 120 aulas
  • 4 projetos
  • 9h 45min

Blog | Mais populares