Voltar ao blog
Agosto 19, 2026Guias

Como implementar aplicações com Docker Compose num VPS

Guia prático de produção do Docker Compose v2 no Ubuntu: instalar o motor, escrever um ficheiro Compose com redes e volumes, tratar de segredos, colocar um proxy inverso à frente, atualizar sem pânico e fazer cópias dos volumes com nome.

Como implementar aplicações com Docker Compose num VPS

O Docker Compose é a forma habitual de correr uma pilha pequena num VPS: uma aplicação web, uma base de dados, uma cache Redis e um proxy inverso, descritos num ficheiro YAML. Não é Kubernetes. Não terá comutação multi-nó. Terá um arranjo repetível e revisto, que pode recriar em minutos num VPS Hiddence novo — exatamente o que a maioria dos projetos paralelos e produtos pequenos precisa.

Este artigo assume que quer algo mais próximo da produção do que um docker run numa sexta à noite. Vamos instalar o Docker Engine e o plugin Compose, criar um utilizador de implementação sem root, escrever um ficheiro Compose com healthchecks e políticas de reinício, manter os segredos fora do Git, publicar na internet só as portas do proxy inverso e definir uma rotina de atualização e cópia de segurança. A pilha de exemplo é uma aplicação web típica mais PostgreSQL mais Caddy, mas o mesmo padrão serve para Node, PHP, Python ou um conjunto de workers.

Porque o Compose num único VPS ainda faz sentido

As pessoas saltam para o Kubernetes porque está na moda e depois passam o fim de semana em YAML para um sítio. O Compose continua legível. Versiona o ficheiro, documenta variáveis de ambiente e pode fazer um diff da alteração antes de a aplicar. O isolamento chega: um contentor da aplicação comprometido não deveria deter o socket UNIX da base se usou redes e utilizadores com o mínimo de privilégio. Os limites de recursos impedem que uma fuga de memória congele todo o VPS.

  • Um ficheiro descreve toda a pilha
  • Os volumes com nome sobrevivem à recriação de contentores
  • As redes internas do Docker mantêm o Postgres fora da internet pública
  • restart: unless-stopped cobre a maioria dos reinícios
  • Fácil de copiar para um segundo VPS quando o primeiro fica pequeno

Requisitos

Use um VPS com RAM suficiente para a aplicação mais o Postgres. Um plano de 2 GB é um mínimo realista para aplicação + base + proxy. Assume-se Ubuntu 24.04. Não instale o Docker a partir de um Snap qualquer sem ler o que faz aos cgroups; os passos abaixo usam o repositório apt oficial da Docker.

  • Ubuntu 22.04/24.04, root ou sudo
  • Recomendam-se 2 GB de RAM (1 GB só para pilhas minúsculas sem Postgres)
  • Um domínio a apontar para o VPS se quiser HTTPS automático
  • Git ou outra forma de copiar o projeto para o servidor

Passo 1: Instalar o Docker Engine e o Compose v2

O Compose v2 é um plugin invocado como docker compose (com um espaço), não o antigo binário Python docker-compose. Instale ambos a partir do repositório da Docker para receber correções de segurança atuais.

bash
ssh root@YOUR_VPS_IP
apt update && apt -y install ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt update
apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

docker version
docker compose version

Passo 2: Utilizador de implementação e estrutura de diretórios

Correr o Compose como root funciona, mas um utilizador dedicado no grupo docker é mais limpo. Coloque o projeto em /opt ou /srv, não em /root, para as cópias e permissões ficarem óbvias. Nunca deixe com escrita mundial o diretório que contém .env.

bash
adduser --disabled-password --gecos '' deploy
usermod -aG docker deploy
mkdir -p /srv/app
chown deploy:deploy /srv/app
chmod 750 /srv/app

# Log in as deploy for the rest of the file editing:
# su - deploy
# cd /srv/app

Passo 3: Escrever um ficheiro Compose pensado para produção

O ficheiro abaixo é um modelo. Substitua a imagem da aplicação pela sua. O Postgres não é publicado em 0.0.0.0:5432 — só o Caddy é publicado nas 80 e 443. Os healthchecks impedem que um proxy inverso envie tráfego a uma aplicação que ainda está a migrar a base. Fixe as etiquetas de imagem; latest é como acontecem as quebras de surpresa.

bash
# /srv/app/compose.yaml
services:
  caddy:
    image: caddy:2.8-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      app:
        condition: service_healthy

  app:
    image: ghcr.io/example/webapp:1.4.2
    restart: unless-stopped
    env_file: .env
    environment:
      DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"]
      interval: 10s
      timeout: 3s
      retries: 10
    networks:
      - frontend
      - backend

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    env_file: .env
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - backend

networks:
  frontend: {}
  backend: {}

volumes:
  pgdata:
  caddy_data:
  caddy_config:

Passo 4: Segredos, .env e Caddyfile

Coloque as palavras-passe em .env no servidor, chmod 600, e nunca faça commit desse ficheiro. Gere palavras-passe com openssl rand -base64 32. O Caddyfile só precisa de fazer proxy inverso para o nome de serviço da aplicação na rede Docker.

bash
# /srv/app/.env (permissions 600)
POSTGRES_USER=app
POSTGRES_PASSWORD=change-me-long-random
POSTGRES_DB=app
# plus any APP_SECRET / NEXTAUTH_SECRET your image needs

# /srv/app/Caddyfile
app.example.com {
    encode gzip
    reverse_proxy app:8000
}

chmod 600 /srv/app/.env
cd /srv/app
docker compose up -d
docker compose ps
docker compose logs -f --tail=100

Passo 5: Firewall, registos e limites de recursos

O UFW só deve permitir 22, 80 e 443. O Docker por vezes contorna o UFW nas portas publicadas; se precisar de uma firewall de anfitrião rigorosa, consulte a interação atual Docker + UFW da sua versão do Ubuntu ou publique portas só em 127.0.0.1 e coloque um Caddy de anfitrião à frente. Defina limites de memória no Compose para o Postgres não comer o VPS.

bash
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

# Optional in compose.yaml under app or db:
#    deploy:
#      resources:
#        limits:
#          memory: 512M

# Logs (do not let json-file grow forever):
# { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }
# in /etc/docker/daemon.json then: systemctl restart docker

Passo 6: Atualizações e reversões

A atualização aborrecida que funciona: obter imagens novas, up -d, vigiar healthchecks, manter a etiqueta anterior no git para poder voltar a fixá-la. Não execute docker compose down numa base de produção salvo se pretender deixar de aceitar tráfego. down -v apaga volumes — isso é uma limpeza, não um reinício.

bash
cd /srv/app
git pull   # if the Compose file lives in git

# Change the image tag in compose.yaml, then:
docker compose pull
docker compose up -d
docker compose ps

# Rollback: set the old tag, pull, up -d again
# NEVER: docker compose down -v   # this deletes named volumes

Cópias de segurança dos volumes com nome

Uma instantânea do VPS é boa. Um despejo lógico do Postgres é melhor se precisar de restaurar noutra máquina. Agende isto com cron. Teste um restauro uma vez; caso contrário não tem cópias de segurança — tem ficheiros que espera que o sejam.

bash
# Postgres dump while the db container is running:
docker compose exec -T db pg_dump -U app app | gzip > /var/backups/app-$(date +%F).sql.gz

# Copy off-box
# scp /var/backups/app-*.sql.gz backup-host:~

# Restore sketch (maintenance window):
# gunzip -c app-2026-08-19.sql.gz | docker compose exec -T db psql -U app app

Resolução de problemas

Leia docker compose ps e docker compose logs service. Falhas ao obter imagens são normalmente limites de taxa do ghcr/Docker Hub ou uma imagem privada sem início de sessão. Um 502 do Caddy significa que a aplicação não está saudável ou que o nome de anfitrião do proxy está errado (use o nome de serviço Compose, não localhost, a partir do contentor Caddy). Erros de permissão em volumes significam muitas vezes que a imagem corre como uid 1000 enquanto o diretório do anfitrião é de root.

  • compose: command not found — instalou o nome antigo do binário; use docker compose
  • port is already allocated — outra coisa possui 80/443 (Apache, Nginx, outro Caddy)
  • database connection refused — a aplicação arrancou antes de o Postgres estar pronto; use healthcheck + condição depends_on
  • disk full — docker system df, depois prune cuidadoso de imagens não usadas
  • permission denied on docker.sock — utilizador fora do grupo docker, ou precisa de uma nova sessão de acesso

Segurança

Não publique portas de base de dados. Não use privileged: true. Não monte /var/run/docker.sock num contentor da aplicação salvo se estiver a escrever de propósito um gestor de contentores. Mantenha o motor atualizado. Analise as suas próprias imagens se as construir. .env no disco continua a ser um ficheiro secreto — restrinja quem pode entrar por SSH na máquina.

  • Sem 5432 / 3306 / 6379 públicos
  • Fixe digestos de imagem ou pelo menos etiquetas imutáveis
  • chmod 600 .env
  • Unattended-upgrades do sistema anfitrião ainda importam
  • Um projeto Compose por aplicação reduz o raio de impacto

Dicas

  • Coloque compose.yaml no git; mantenha .env.example sem segredos reais
  • Use perfis para workers opcionais (docker compose --profile workers up -d)
  • Observe docker stats quando dimensionar o VPS
  • Montagens bind do código-fonte não são o padrão de produção — construa uma imagem
  • Se a pilha crescer para vários VPS, então olhe para orquestração — não antes

Um arranjo Compose quase de produção num VPS é: pacotes oficiais Docker, um .env protegido, um YAML que não publica a base, healthchecks, um proxy inverso nas 80/443, UFW, etiquetas de imagem às quais pode voltar e despejos Postgres que restaurou de verdade uma vez. Parta do modelo deste artigo, substitua a imagem da aplicação e trate docker compose down -v como um comando destrutivo, com o mesmo respeito que rm -rf.