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:
- Imagem com versão.
app:1.8.3sobe 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. - 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
dbna rede que o Compose cria. - Segredo fora do arquivo versionado.
app.enve a pastasegredos/ficam fora do git e com permissão restrita. A senha nunca vai para o Dockerfile; veja como o segredo chega em runtime. - Volume nomeado para cada pasta com estado. Banco em
pgdata, arquivos enviados emuploads. - Política de reinício.
unless-stoppedfaz 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:5432expõ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 -vpor 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 versionrespondendo. - Todo serviço descrito num
compose.yamlversionado. - 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.jsone containers recriados depois disso. - Teste de
downeupfeito, 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?
Referência: https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-debian-10