Docker numa VPS: o que realmente muda
Docker numa VPS usa o mesmo engine, mas a RAM acaba, portas publicadas ignoram a UFW, contentores não reiniciam e o disco pode ficar cheio.
O que muda quando executa Docker numa VPS
O Docker numa VPS usa o mesmo engine e as mesmas imagens que o Docker no seu portátil, por isso todos os comandos que já conhece continuam a funcionar. O que muda é o espaço disponível à volta dele. Um portátil tem memória livre, uma firewall que ninguém está a analisar e um disco suficientemente grande para nunca precisar de o consultar. Um servidor alugado tem um limite fixo de memória, um endereço IP público que começa a ser analisado poucos minutos depois do arranque e um sistema de ficheiros raiz que o Docker vai preencher sem pedir autorização.
Quatro diferenças causam a maioria dos problemas numa máquina pequena:
- A memória é finita e o kernel resolve uma falta de memória terminando um processo.
- Uma porta publicada passa diretamente pela UFW (uncomplicated firewall), porque o Docker escreve as suas próprias regras de firewall.
- Os contentores não voltam a arrancar depois de um reboot, a menos que tenha configurado isso antecipadamente.
- As imagens, os contentores, os volumes e a cache de build crescem até o disco ficar cheio.
Cada secção abaixo identifica a falha, a mensagem que verá efetivamente e o guia que a corrige em profundidade. Se ainda não escreveu um ficheiro Compose, leia primeiro Noções básicas do Docker Compose numa VPS e volte depois. Esta página pressupõe que já consegue iniciar uma stack.
Quanta RAM um contentor Docker utiliza?
Menos do que a maioria das pessoas espera. Um contentor é um processo num cgroup (grupo de controlo), não uma máquina virtual. Por isso, não existe um kernel convidado nem uma alocação fixa. O custo corresponde ao que o processo no seu interior utiliza. É por isso que uma stack completa cabe em 2 GB quando a mesma stack criada com máquinas virtuais não caberia.
Os valores abaixo são números típicos em idle para imagens padrão no Ubuntu 24.04, com a configuração predefinida, lidos a partir de docker stats alguns minutos depois do arranque. Servem como ponto de partida para o planeamento, não como benchmark da sua carga de trabalho. Execute docker stats --no-stream no seu próprio servidor antes de confiar em qualquer valor, incluindo estes.
The data behind this chart
[
{
"label": "nginx",
"idle_mb": 8,
"budget_mb": 64
},
{
"label": "Redis 7",
"idle_mb": 12,
"budget_mb": 128
},
{
"label": "Traefik v3",
"idle_mb": 40,
"budget_mb": 128
},
{
"label": "PostgreSQL 16",
"idle_mb": 45,
"budget_mb": 512
},
{
"label": "Uptime Kuma",
"idle_mb": 95,
"budget_mb": 256
},
{
"label": "MariaDB 11",
"idle_mb": 190,
"budget_mb": 512
},
{
"label": "Nextcloud (Apache)",
"idle_mb": 210,
"budget_mb": 768
}
]As duas colunas têm funções diferentes. idle_mb indica o que o contentor utiliza quando não está a fazer nada. budget_mb indica quanto deve reservar no planeamento, porque a utilização real não ocorre em idle. O PostgreSQL fica perto de 45 MB em idle e precisa de 512 MB quando as ligações, ordenações e a cache estão ativas. Faça o planeamento com a coluna de orçamento. Faça a depuração com a coluna de idle.
Observe o perfil dessas 7 linhas. O nginx fica em idle com 8 MB e o Nextcloud com 210 MB. O proxy à frente das suas aplicações consome muito pouca memória. São a base de dados e a aplicação PHP que determinam a dimensão necessária do servidor.
Tenha atenção a docker stats: o valor de memória inclui a cache de páginas carregada pelas leituras de ficheiros do próprio contentor, por isso aumenta durante algum tempo depois do arranque e depois estabiliza. Monitorize-o durante uma hora antes de concluir que existe uma fuga de memória.
Dimensionar um VPS: o que cabe em 2 GB, 4 GB e 8 GB
Comece por reservar a parte do host. O kernel, o systemd, o journald, o sshd e o daemon do Docker usam a mesma RAM que os seus contentores, e dockerd com containerd ocupam cerca de 100 MB. Também precisa de memória livre para a cache de páginas e para o pico de utilização que ocorre quando é criada uma imagem ou executado um dump da base de dados.
The data behind this chart
[
{
"plan": "2 GB VPS",
"total_mb": 2048,
"host_reserve_mb": 768,
"container_mb": 1280
},
{
"plan": "4 GB VPS",
"total_mb": 4096,
"host_reserve_mb": 1024,
"container_mb": 3072
},
{
"plan": "8 GB VPS",
"total_mb": 8192,
"host_reserve_mb": 1536,
"container_mb": 6656
}
]host_reserve_mb cobre o sistema operativo, o daemon do Docker e a margem que mantém o servidor responsivo sob carga. O que sobra é container_mb, e esse é o único valor que pode utilizar. A reserva aumenta com o plano, de 768 MB no servidor mais pequeno para 1536 MB no maior, porque um servidor maior executa mais contentores, escreve mais logs e precisa de mais cache de páginas.
Um plano de 2 GB deixa 1280 MB para os contentores. Reserve 512 MB para o PostgreSQL e 128 MB para o Traefik, e metade desse valor já desapareceu. O restante chega para duas aplicações pequenas, com cerca de 256 MB cada. É um servidor real e útil. Não é espaço suficiente para acrescentar o Nextcloud e um cluster de pesquisa.
Um plano de 4 GB deixa 3072 MB, o suficiente para executar ao mesmo tempo uma base de dados, um reverse proxy, três aplicações e um contentor de monitorização. Este é o menor tamanho que vale a pena usar para algo importante, porque a memória livre absorve os efeitos de uma implementação problemática.
Um plano de 8 GB deixa 6656 MB dos seus 8192 MB, e o limite normalmente passa da memória para a CPU ou para o débito do disco. Há um tipo de contentor que dimensiona o consumo a partir da configuração, e não da carga: um servidor de modelos local reserva cache KV proporcionalmente à sua janela de contexto, por isso aumentar o num_ctx do Ollama pode acrescentar gigabytes ao orçamento antes de chegar um único pedido. Se os cálculos mostrarem que a sua stack não cabe, compre o plano maior em vez de tentar contornar o problema com afinações: quanto custa realmente um VPS explica o valor mensal dos gigabytes adicionais.
Duas regras mantêm os cálculos realistas. Defina um limite de memória para cada serviço, para que um processo descontrolado não derrube todo o servidor. E não utilize a totalidade do orçamento, porque docker compose build e pg_dump precisam de memória precisamente no pior momento. Limites de memória no Docker Compose apresenta a sintaxe e as armadilhas.
Porque é que o meu container termina com o código 137?
Porque o kernel o terminou. 137 é 128 mais 9, e o sinal 9 é SIGKILL. O container pediu mais memória do que aquela que lhe era permitida, e o OOM killer terminou-o.
CONTAINER ID IMAGE STATUS
9f2c1a4d7b3e postgres:16 Exited (137) 4 minutes agoConfirme a causa em vez de adivinhar:
docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory""OOMKilled": true significa que o container atingiu o seu próprio limite de cgroup, e o log do kernel identifica o processo escolhido:
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kBEste é o caso favorável, porque o impacto ficou limitado a um container. O caso problemático é um container sem qualquer limite. Sem um limite, o teto é a memória de toda a máquina. Uma fuga de memória num serviço esgota os recursos do host, e o kernel escolhe depois uma vítima com base no consumo em todo o sistema. A linha do log perde o prefixo Memory cgroup e fica Out of memory: Killed process 2417 (postgres). O processo escolhido é frequentemente a sua base de dados, enquanto o container que causou a fuga continua a executar. Por isso, definir um limite para todos os serviços é mais importante do que acertar no valor exato de qualquer limite individual.
A swap altera o momento em que o problema surge, não a aritmética. A maioria das imagens VPS é fornecida sem swap. Verifique com swapon --show, que não imprime nada quando não existe swap. Um ficheiro de swap dá ao kernel um local para colocar páginas inativas, o que lhe compra alguns minutos para detetar o problema.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hfree -h deve agora mostrar um total diferente de zero na linha Swap. A swap não adiciona RAM. Uma máquina sob pressão constante de memória fica suficientemente lenta para impedir o acesso por SSH e a correção do problema. Trate a swap como uma margem de segurança e corrija o dimensionamento.
Por que o UFW não bloqueia a porta publicada pelo Docker?
Porque o tráfego nunca chega à cadeia protegida pelo UFW. Quando publica uma porta com -p 5432:5432 ou com uma entrada ports: do compose, o daemon escreve uma regra DNAT (tradução de endereços de rede de destino) na tabela nat e uma regra de aceitação na sua própria cadeia DOCKER. Um pacote destinado a um contentor é encaminhado para esse contentor, em vez de ser entregue ao host. Por isso, segue o caminho FORWARD e nunca passa pelas regras INPUT escritas pelo UFW.
Pode observar este comportamento no servidor:
sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432O UFW pode mostrar 5432 DENY IN Anywhere enquanto a tabela nat contém uma regra DNAT tcp ... to:172.18.0.2:5432 para a mesma porta. A partir de outra máquina, nc -vz your.server.ip 5432 continua a estabelecer ligação. A base de dados está acessível através da Internet pública, embora a firewall indique o contrário.
A correção é publicar menos portas. Os contentores do mesmo projeto compose partilham uma rede e conseguem aceder uns aos outros pelo nome do serviço. Por isso, uma base de dados que serve apenas a aplicação ao lado não precisa de qualquer entrada ports:. Quando precisar de acesso local, associe a publicação à interface de loopback:
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"Depois de docker compose up -d, nc -vz your.server.ip 5432 falha a partir do exterior, enquanto psql -h 127.0.0.1 -p 5432 continua a funcionar no host. Numa stack pequena e corretamente configurada, apenas o reverse proxy publica portas, nas portas 80 e 443. Por que as portas publicadas pelo Docker ignoram o UFW explica a cadeia DOCKER-USER nos casos em que é necessário publicar uma porta e filtrá-la na mesma. Noções básicas da firewall UFW explica as regras do host subjacentes.
Por que os meus containers desaparecem depois de um reboot?
Porque nada lhes disse para voltarem a arrancar. Um container é criado com a política de reinício no se não definir outra, por isso um reboot deixa-o parado e o daemon não intervém. Os reboots não são raros numa VPS: atualizações do kernel através de unattended upgrades, manutenção do fornecedor e a sequência OOM acima terminam todos num deles.
Duas condições têm de ser cumpridas. O daemon tem de arrancar no boot:
systemctl is-enabled dockerIsto apresenta enabled numa instalação Ubuntu padrão. Depois, cada serviço precisa de uma política:
services:
app:
image: ghcr.io/example/app:1.4
restart: unless-stoppedunless-stopped volta a arrancar o container depois de um reboot e respeita um container que parou de propósito. always também reinicia os containers que parou deliberadamente sempre que o daemon reinicia, o que pode ser inesperado durante a depuração. Editar o ficheiro não é suficiente, porque a política de reinício é definida quando o container é criado. Execute docker compose up -d para o recriar e, depois, confirme o valor ativo:
docker inspect my-app | grep -A3 RestartPolicyDepois, faça reboot do servidor de propósito e execute docker compose ps no diretório do projeto. Uma stack que sobrevive a um reboot planeado também sobrevive a um reboot não planeado. Se a sua stack precisar de uma garantia de ordem ou de um job executado uma única vez no boot, uma unidade systemd é uma ferramenta melhor: iniciar Docker Compose no boot inclui o ficheiro da unidade. Para confirmar se um container que voltou a arrancar está efetivamente a servir pedidos, adicione healthchecks do Compose.
Por que o disco do meu VPS está cheio?
Porque o Docker mantém tudo até lhe indicar o contrário. Cada tag de imagem que alguma vez descarregou, cada contentor parado, cada volume anónimo deixado por uma recriação e cada camada da cache de compilação permanecem no disco. Num sistema de ficheiros root de 40 GB ou 80 GB, o que é normal nestas dimensões de plano, isso transforma-se numa interrupção do serviço em meses, e não em anos.
Um disco cheio não se manifesta como uma falha do sistema. Na mesma hora, recebe no space left on device de um contentor, de apt, do journald e de docker pull. O PostgreSQL deixa de aceitar escritas. O servidor continua ativo, o que torna o problema mais difícil de detetar do que um ciclo contínuo de reinícios.
Verifique antes de apagar:
docker system df
df -h /docker system df divide o total entre imagens, contentores, volumes locais e cache de compilação, com uma coluna RECLAIMABLE junto de cada categoria. Num servidor que cria as próprias imagens, a cache de compilação costuma ser a maior categoria.
docker image prune -a
docker builder prune
docker system dfdocker image prune -a remove todas as imagens que nenhum contentor está a utilizar. docker builder prune limpa a cache de compilação. Ambos são seguros enquanto os serviços estão em execução, porque os elementos em uso são ignorados. O que não é seguro é docker system prune --volumes, que elimina todos os volumes que nenhum contentor referencia neste momento. Uma stack que parou durante o fim de semana tem exatamente essa situação, e o respetivo volume da base de dados é eliminado. Leia diferenças entre bind mounts e volumes nomeados antes de utilizar essa flag e faça primeiro uma cópia de segurança.
Os logs dos contentores são uma fonte de crescimento mais discreta. O driver json-file predefinido não tem limite de tamanho, por isso um contentor com muitos logs pode escrever gigabytes em /var/lib/docker/containers. Defina um limite para todos os contentores em /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Aplique a alteração com sudo systemctl restart docker. Este comando reinicia os seus contentores, por isso escolha o momento adequado. O limite aplica-se aos contentores criados depois da alteração. Recrie os contentores em execução com docker compose up -d --force-recreate e confirme:
docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containersO resultado de inspect deve mostrar max-size definido. Se estiver vazio, esse contentor foi criado antes da alteração e continua a escrever sem limite.
Hábitos que mantêm um pequeno servidor Docker saudável
Nada disto exige um dashboard ou uma ferramenta que tenha de aprender.
- Execute
docker system dfedf -h /no primeiro dia de cada mês. São dois comandos, demoram trinta segundos e permitem identificar a tendência muito antes de esta causar uma indisponibilidade. - Defina um limite de memória para todos os serviços, incluindo os que tem a certeza de serem pequenos. O limite transforma uma indisponibilidade de todo o host num único contentor reiniciado.
- Monitorize o servidor a partir de outro local, para ser avisado da pressão sobre a memória ou o disco antes de o kernel atuar. O Uptime Kuma é executado num contentor e fica inativo com cerca de 95 MB.
- Faça cópias de segurança dos volumes, não dos contentores. O contentor é descartável, mas o volume não. cópias de segurança restic num VPS abrange a definição de um agendamento e um teste de restauro.
- Fixe as tags das imagens no ficheiro compose e atualize-as num dia escolhido por si. Com
latest, a versão obtida no próximodocker compose pullé a que foi lançada nessa manhã.
Um VPS pequeno com Docker mantém-se saudável durante anos quando quatro valores permanecem dentro dos limites: o orçamento de memória, a lista de portas publicadas, a política de reinício de cada serviço e o espaço livre em disco. Tudo o resto é o mesmo Docker que já executa em casa.
FAQ
Quanta RAM preciso para executar o Docker numa VPS?
O Docker em si consome poucos recursos. O daemon e o containerd juntos ocupam cerca de 100 MB; o restante depende dos seus contentores. Reserve primeiro a parte do host: 768 MB numa máquina com 2048 MB para o sistema operativo, o daemon e alguma margem. Isso deixa 1280 MB para os contentores. Uma base de dados com 512 MB, um reverse proxy com 128 MB e duas aplicações pequenas cabem nesse limite. Meça a sua própria stack com docker stats --no-stream em vez de confiar em valores publicados.
Posso executar o Docker numa VPS com 1 GB?
Sim, para um ou dois contentores leves. Crie também um ficheiro de swap antes de começar. Cerca de metade de uma máquina com 1 GB fica ocupada depois de arrancarem o sistema operativo e o daemon do Docker. Sobra espaço para uma aplicação pequena e um reverse proxy, mas não para uma base de dados sob carga real. A criação de imagens numa máquina desse tamanho pode falhar ou terminar outro processo. Por isso, crie as imagens noutro local e faça pull da imagem final.
O UFW protege um contentor Docker?
Não protege as portas que publica. O Docker cria as suas próprias regras de DNAT e de encaminhamento. Assim, um pacote destinado a uma porta publicada do contentor é encaminhado para o contentor, em vez de ser entregue ao host. As regras INPUT geridas pelo UFW nunca o veem. ufw deny 5432 pode estar ativo enquanto essa porta responde a partir da internet. Publique a porta na loopback com 127.0.0.1:5432:5432, mantenha os serviços internos sem publicação ou filtre na cadeia DOCKER-USER.
Os meus contentores reiniciam depois de um reboot da VPS?
Só se tiverem sido criados com uma política de reinício. Defina restart: unless-stopped em cada serviço, execute docker compose up -d para recriar os contentores com essa política e confirme que systemctl is-enabled docker apresenta enabled. Depois, faça um reboot de propósito e verifique docker compose ps. Uma política de reinício que nunca foi testada não é uma política de reinício.
Com que frequência devo remover imagens do Docker?
Uma vez por mês é suficiente para a maioria dos servidores pequenos. Também pode fazê-lo quando docker system df indicar espaço recuperável que lhe faça falta. docker image prune -a e docker builder prune são seguros enquanto os serviços estão em execução, porque ignoram as imagens e a cache em uso. Evite docker system prune --volumes, a menos que saiba exatamente quais os volumes que não têm referências, porque o comando elimina os dados de qualquer stack que esteja parada.