Programação Por Equipe DevBras

Capa do artigo: Docker no servidor: o primeiro container não é a arquitetura inteira

Docker no servidor: o primeiro container não é a arquitetura inteira

Instale o Docker pelo repositório oficial e descreva o serviço em Compose com volume, porta presa ao localhost e log com limite, para o dado sobreviver à recriação.

Você instalou o Docker na VPS, rodou um docker run e a página abriu. No dia seguinte alguém removeu o container e subiu outro para trocar uma variável, e o banco voltou vazio. A resposta curta: o container é descartável por definição. Tudo o que precisa sobreviver (banco, upload, sessão gravada em arquivo) tem de ficar num volume declarado. Além disso, a imagem precisa de versão fixa, a porta tem de ser publicada de propósito e o segredo entra quando o container sobe, não dentro da imagem. Subir o primeiro container prova o caminho. Não é a arquitetura.

A seguir está o mínimo para um servidor único rodar containers sem perder dado na primeira recriação: instalação pelo repositório oficial, um arquivo Compose com volume nomeado, porta presa ao localhost, log com limite e um teste de recriação que você roda antes de confiar.

Por que o dado some quando o container é recriado

Cada container tem uma camada gravável própria em cima da imagem. Tudo o que o processo escreve fora de um volume vai para essa camada. Parar e iniciar o mesmo container preserva a camada. Remover o container apaga a camada junto. E recriar é rotina: trocar variável de ambiente, atualizar a imagem ou mudar a porta exige um container novo. O docker compose up -d recria o serviço sozinho quando a configuração muda, sem perguntar nada.

Volume é o diretório que vive fora desse ciclo. Um volume nomeado é gerenciado pelo Docker e sobrevive à remoção do container. Um bind mount aponta para uma pasta do host que você escolhe. Os dois servem. O que não serve é nenhum dos dois.

Junto com esse mal-entendido aparecem outros três. A porta publicada com -p 5432:5432 escuta em todas as interfaces do host, e o Docker escreve as regras de iptables diretamente, então o UFW que você configurou não bloqueia essa porta. Quem está no grupo docker tem, na prática, poder de root no host, porque pode montar a raiz do sistema dentro de um container. E o log que o processo escreve na saída padrão é guardado pelo Docker em arquivo JSON sem limite de tamanho, a não ser que você configure um.

Como montar o primeiro serviço do jeito certo

Instale pelo repositório oficial do Docker

O pacote da distribuição funciona, mas costuma vir atrasado e sem o plugin do Compose atual. No Debian, a documentação do Docker indica adicionar o repositório deles:

sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo 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/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

No Ubuntu, troque debian por ubuntu nas duas URLs. Confira com docker version e docker compose version. Os dois precisam responder sem erro antes de você seguir.

Descreva o serviço num arquivo Compose

Comando solto no histórico do shell não é documentação. Um compose.yaml versionado diz qual imagem, qual porta, qual volume e qual configuração cada serviço usa. Exemplo com uma aplicação e um PostgreSQL:

services:
  app:
    image: registry.exemplo.com.br/loja/app:1.8.3
    restart: unless-stopped
    env_file: ./app.env
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - uploads:/app/uploads
    depends_on:
      - db

  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: loja
      POSTGRES_DB: loja
      POSTGRES_PASSWORD_FILE: /run/secrets/db_senha
    secrets:
      - db_senha
    volumes:
      - pgdata:/var/lib/postgresql/data

secrets:
  db_senha:
    file: ./segredos/db_senha.txt

volumes:
  pgdata:
  uploads:

O que cada decisão resolve:

  1. Imagem com versão. app:1.8.3 sobe sempre a mesma coisa. Uma tag que muda de conteúdo sobe o que estiver publicado na hora; o motivo está em por que latest não é uma versão.
  2. Porta presa ao 127.0.0.1. A aplicação só é alcançada pelo proxy do próprio host (Nginx, Caddy), que cuida do HTTPS. O banco nem publica porta: a aplicação o encontra pelo nome db na rede que o Compose cria.
  3. Segredo fora do arquivo versionado. app.env e a pasta segredos/ ficam fora do git e com permissão restrita. A senha nunca vai para o Dockerfile; veja como o segredo chega em runtime.
  4. Volume nomeado para cada pasta com estado. Banco em pgdata, arquivos enviados em uploads.
  5. Política de reinício. unless-stopped faz o serviço voltar depois de um reboot do host, a não ser que você o tenha parado de propósito.

Limite o log antes que ele encha o disco

Crie ou edite /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Reinicie o daemon com sudo systemctl restart docker. A regra vale para containers criados depois dela. Os que já existem precisam ser recriados para herdar o limite. A aplicação continua escrevendo log na saída padrão, e você lê com docker compose logs -f app.

Rode o processo sem root e cuide de quem acessa o Docker

A imagem deve definir um usuário sem privilégio para o processo; o passo a passo está em o processo no container não precisa ser root. No host, só entra no grupo docker quem você aceitaria como root. E não monte /var/run/docker.sock dentro de um container sem saber exatamente por quê: quem fala com esse socket controla o host.

Faça o teste de recriação

Antes de colocar dado real, prove que ele sobrevive:

docker compose up -d
docker compose exec db psql -U loja -d loja -c "CREATE TABLE IF NOT EXISTS teste (id int); INSERT INTO teste VALUES (1);"
docker compose down
docker compose up -d
docker compose exec db psql -U loja -d loja -c "SELECT * FROM teste;"

Se o banco ainda estiver iniciando, o psql recusa a conexão; espere alguns segundos e repita. O down sem opção remove containers e rede, mas mantém os volumes nomeados. Por isso o registro deve aparecer no final. Já o docker compose down -v apaga os volumes junto. Não deixe esse comando num script de deploy.

Para achar pasta esquecida, use docker diff com o nome do container. Ele lista o que mudou na camada gravável. Se aparecer algo como /app/storage/sessions, isso some no próximo recreate.

Volume resolve recriação, não desastre. Se o disco do servidor morre, o volume morre junto. Backup é uma cópia em outro lugar, com restauração testada.

Quando você passa a ter mais de um servidor

Com dois ou três hosts, o Compose continua bom em cada um. A pergunta é que muda: o que está rodando onde, em que versão, e quem pode reiniciar o quê. O próprio Docker ajuda com contextos, que apontam o cliente local para um daemon remoto por SSH:

docker context create vps2 --docker "host=ssh://deploy@vps2.exemplo.com.br"
docker --context vps2 ps

Se preferir uma interface web, o Dockyard, painel da própria DevBras para Docker em vários hosts, usa um agent em cada servidor que liga para o painel sem abrir porta de entrada, sobe stacks Compose, separa papéis admin, operator e viewer, e o container é gratuito.

Qualquer que seja a ferramenta, as regras deste guia continuam valendo em cada host: versão fixa, volume declarado, porta de propósito, segredo fora da imagem.

Erros comuns

  • Banco sem volume. Funciona até o primeiro recreate, que acontece na primeira troca de variável ou de imagem. O dado vai junto com o container.
  • Publicar a porta do banco em todas as interfaces. 5432:5432 expõe o PostgreSQL para a internet mesmo com o UFW bloqueando, porque o Docker escreve as próprias regras de firewall.
  • Atualizar com apt dentro do container. A correção some no próximo recreate e a imagem continua velha. Atualizar é gerar imagem nova e trocar o container.
  • Log sem limite. Um serviço verboso enche o disco em semanas, e o banco para de gravar junto com todo o resto.
  • down -v por hábito. Em desenvolvimento limpa tudo. Em produção, apaga o banco.
  • Colocar todo mundo no grupo docker. É distribuir acesso de root sem chamar de root.

Checklist

  • Docker e Compose instalados pelo repositório oficial, com docker compose version respondendo.
  • Todo serviço descrito num compose.yaml versionado.
  • Toda imagem com tag de versão, nenhuma com latest.
  • Portas publicadas apenas em 127.0.0.1, exceto o proxy.
  • Cada pasta com estado mapeada para um volume, conferida com docker diff.
  • Limite de log configurado no daemon.json e containers recriados depois disso.
  • Teste de down e up feito, com o registro de teste ainda presente.
  • Backup dos volumes saindo do servidor, com uma restauração já ensaiada.

Rode docker diff no seu container principal agora. Qual pasta sua ainda está só dentro do container?

Avalie este artigo

Referência: https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-debian-10