Podman ou Docker em uma VPS: o que muda de verdade
Podman não usa daemon e roda rootless por padrão. Veja o impacto em compose, quadlets, portas abaixo de 1024 e propriedade de volumes na VPS.
O que realmente difere entre Podman e Docker
Podman e Docker executam as mesmas imagens OCI (open container initiative) numa VPS, por isso a escolha não depende do software que pode executar. A diferença está no modelo de processos. O Docker executa um daemon root que gere todos os contentores, e o comando docker é um cliente pequeno que pede a esse daemon para realizar o trabalho. O Podman não tem daemon: podman run inicia o contentor como um processo filho do processo que o chamou, sob o seu próprio utilizador sem privilégios.
Tudo o resto resulta desse facto. O arranque automático passa a ser responsabilidade do systemd, em vez do daemon. A propriedade dos volumes passa por um espaço de nomes de utilizadores, por isso o proprietário que vê com ls -l no host não é o proprietário que o contentor vê. As portas abaixo de 1024 recusam ligações até alterar uma definição do kernel. A CLI (interface de linha de comandos) docker continua a funcionar através de um wrapper, até ao momento em que algo tenta aceder ao socket do Docker.
Sem daemon: o que é executado quando se inicia um container
Num host Docker, pstree -a mostra dockerd como root, containerd ao lado dele e um containerd-shim-runc-v2 por cada container em execução. A sua aplicação é filha desse shim, e o shim é filho do PID 1. Nada liga o container à shell que o iniciou. Se parar o daemon, perde o plano de controlo de todos os containers no host. Com a definição live-restore desativada, systemctl restart docker também reinicia os seus containers.
O Podman não tem um processo equivalente. Inicie um container e obterá um único processo conmon (monitor de containers) que mantém o processo principal do container, pertencente ao utilizador que executou o comando.
podman run -d --name web -p 8080:80 docker.io/library/caddy:2
ps -o user,pid,ppid,args -C conmon
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080ps deve listar conmon em execução com o seu utilizador de login, e não como root. curl deve apresentar 200. Como não existe um serviço central responsável pelo container, sudo apt upgrade podman não para nada que já esteja em execução. Se o monitor de um container falhar, os restantes não são afetados.
A ausência do daemon também tem um custo. Nada inicia os seus containers depois de um reboot. O --restart=always do Docker é uma garantia que o daemon mantém durante o boot. O Podman substitui-o pelo systemd. É esse o objetivo da secção sobre quadlet abaixo.
O socket é a outra parte da questão. /var/run/docker.sock é um endpoint de API (interface de programação de aplicações) pertencente a root. Qualquer processo que possa escrever nele pode iniciar um container privilegiado com o sistema de ficheiros do host montado. Adicionar um utilizador ao grupo docker concede a esse utilizador acesso root por uma via mais lenta. Vale a pena ler isto em conjunto com dar a cada conta de serviço apenas o acesso de que precisa. O Podman não expõe nenhum socket, exceto se o solicitar. O socket obtido pertence a um único utilizador em /run/user/<uid>/podman/podman.sock.
Instalar o Podman no Ubuntu 24.04 e confirmar que o modo rootless está efetivamente ativo
sudo apt update
sudo apt install -y podman uidmap
podman --version
podman info | grep -i rootlessO pacote uidmap fornece newuidmap e newgidmap. Estes são os auxiliares setuid que permitem a um utilizador normal obter um intervalo de IDs subordinados. Sem eles, os contentores rootless não arrancam. podman info deve apresentar rootless: true.
O Ubuntu 24.04 inclui o Podman 4.9 e o Debian 13 inclui o Podman 5.x, conforme verificado em agosto de 2026. A diferença é importante, porque os ficheiros quadlet requerem a versão 4.4 ou posterior e os ficheiros quadlet .pod requerem a versão 5.0. Execute podman --version antes de copiar um exemplo da documentação upstream.
Cada utilizador rootless precisa de um intervalo de IDs subordinados:
grep "$USER" /etc/subuid /etc/subgidUm utilizador criado por adduser no Ubuntu recebe automaticamente um intervalo. Um utilizador criado por useradd -M ou por uma ferramenta de configuração muitas vezes não recebe nenhum, e a falha indica isso:
Error: cannot find UID/GID for user deploy: no subuid ranges found for user "deploy" in /etc/subuid - check rootless mode in man pages.Atribua um intervalo e depois reinicialize o armazenamento desse utilizador para aplicar o novo mapeamento:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrateHá outra surpresa na primeira execução: o Podman não assume o Docker Hub. Um nome curto de imagem é resolvido com base em unqualified-search-registries em /etc/containers/registries.conf. Num script sem um terminal associado, o pull falha com short-name resolution enforced but cannot prompt without a TTY. Escreva sempre o nome completo. Use docker.io/library/nginx:1.27 em vez de nginx.
O que os contentores rootless realmente oferecem num servidor alugado
Um contentor rootless é executado dentro de um namespace de utilizadores, uma funcionalidade do kernel que atribui a um processo o seu próprio mapa privado de IDs de utilizador. Dentro do namespace, o superutilizador do contentor é o UID (ID de utilizador) 0. Fora dele, no seu VPS, esse mesmo processo é o seu utilizador de login normal. O root do contentor não é o root do host.
Esse é o alcance real da vantagem. Uma imagem que exige execução como root, uma aplicação web com uma vulnerabilidade de execução remota de código ou uma fuga que dependa de o processo ter UID 0 fora do contentor acabam por ter as permissões do seu utilizador sem privilégios, e não as da máquina. O rootless não o protege contra vulnerabilidades do kernel. Também não protege os seus próprios ficheiros, porque o processo que escapou está a ser executado como o seu utilizador e pode ler tudo o que esse utilizador consegue ler.
O Docker também pode ser executado em modo rootless. dockerd-rootless-setuptool.sh install configura um daemon por utilizador e funciona bem. A diferença está na opção predefinida. Com o Podman, obtém rootless sem precisar de o pedir. Assim, a primeira falha é um contentor que não consegue associar-se à porta 80, e não um serviço que funcionou silenciosamente como root durante dois anos.
Por que os meus ficheiros de volumes pertencem ao UID 100999?
Isso acontece devido ao mesmo namespace de utilizadores. O UID 0 do contentor é mapeado para o seu UID no host. O UID 1 do contentor é mapeado para o primeiro ID do seu intervalo subuid, e a contagem continua a partir daí. Com um intervalo iniciado em 100000, o UID 1000 do contentor aparece no host como 100999.
mkdir -p "$PWD/data"
podman run --rm --user 1000 -v "$PWD/data:/data" docker.io/library/alpine:3 sh -c 'id -u; touch /data/f'
ls -ln "$PWD/data"O contentor apresenta 1000. A listagem do host apresenta o proprietário 100999, porque 100000 mais 1000 menos 1 é 100999. Não há nada avariado, e um simples chown não resolve o problema, porque o seu utilizador sem privilégios não pode alterar a propriedade dos ficheiros fora do namespace.
Há quatro formas de resolver isto:
podman unshare chown 1000:1000 "$PWD/data"executa o chown dentro do mesmo namespace de utilizadores, onde os números têm o significado esperado pelo contentor.-v "$PWD/data:/data:U"pede ao Podman para corrigir a propriedade do diretório de origem. Use-o num diretório novo, não em dados importantes.--userns=keep-idmapeia o seu UID do host para o mesmo UID dentro do contentor, para que os ficheiros novos fiquem com o seu utilizador como proprietário.- Um volume nomeado, como
-v appdata:/data, evita esta questão, porque o Podman cria o volume no seu próprio armazenamento, já com a propriedade correta.
Se já teve este problema no Docker, trata-se do mesmo problema, mas numa camada acima. As variáveis PUID e PGID expostas por muitas imagens definem o UID usado pelo processo dentro do contentor e, no Podman rootless, esse UID é depois mapeado uma segunda vez. PUID=1000 dentro de um contentor rootless continua a gravar ficheiros no host com o proprietário 100999. Tenha esse segundo mapeamento em conta ao escolher os números, ou mova os dados para um volume nomeado e deixe de se preocupar com isso.
Há mais duas notas sobre montagens. As flags :z e :Z presentes nos exemplos para Fedora e RHEL são opções de relabeling do SELinux, e o Ubuntu usa AppArmor, por isso essas flags não fazem nada nesse sistema. O Podman rootless também não pode montar um diretório do host que o seu utilizador não possa ler. Esse comportamento é intencional, não uma falha.
Por que o Podman sem root se recusa a publicar a porta 80?
Isso acontece porque associar uma porta inferior a 1024 exige um privilégio que o seu utilizador não tem. O erro indica a correção:
Error: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission deniedHá duas soluções. Reduza o limite para todo o host:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system
sysctl net.ipv4.ip_unprivileged_port_startO último comando deve devolver 80. Tenha claro o que essa definição faz: agora, qualquer utilizador da máquina pode associar as portas 80 e 443, e não apenas o utilizador que executa os containers. Num VPS administrado por uma única pessoa, esta é uma compensação aceitável. Num servidor que aloja contas de outras pessoas, não é. A outra solução é publicar na porta 8080 e colocar um reverse proxy à frente. É aí que pretende ter certificados emitidos e renovados pelo certbot no nginx.
A publicação sem root também altera o que a aplicação recebe. O Podman 4.x usa slirp4netns com o gestor de portas rootlesskit por predefinição. As ligações encaminhadas chegam com o endereço de origem reescrito. Por isso, o log de acesso regista todos os visitantes como 10.0.2.100. O Podman 5.0 alterou a predefinição para pasta, que preserva o endereço real do cliente. No 4.x, --network slirp4netns:port_handler=slirp4netns restaura o endereço de origem verdadeiro, com algum custo de desempenho.
Há uma consequência positiva. Uma porta publicada sem root é um socket de escuta normal, pertencente a um processo comum. Por isso, as regras de entrada da firewall aplicam-se a essa porta. O Docker publica portas através da escrita de regras NAT (tradução de endereços de rede), além das próprias regras de aceitação de encaminhamento. É exatamente por isso que uma porta publicada pelo Docker ignora a regra do ufw que deveria bloqueá-la. O Podman com root utiliza uma configuração semelhante e apresenta a mesma armadilha. O modo sem root não.
Os meus ficheiros Docker Compose continuam a funcionar com Podman?
Na maioria dos casos, sim, através de duas abordagens diferentes. A primeira é podman-compose, uma implementação separada que lê o mesmo ficheiro e utiliza a CLI do Podman:
sudo apt install -y podman-compose
podman-compose up -d
podman psA segunda é o Docker Compose real a comunicar com a API compatível com Docker do Podman através de um socket por utilizador:
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix:///run/user/$(id -u)/podman/podman.sock"
docker compose up -d
podman psdocker compose ps e podman ps devem listar os mesmos contentores, porque existe apenas um conjunto de contentores. A resolução de nomes também funciona: o backend de rede predefinido do Podman, netavark, executa aardvark-dns, pelo que os contentores numa rede definida pelo utilizador se encontram pelo nome.
Existem limitações concretas. Tudo o que montar /var/run/docker.sock tem de apontar para o socket do Podman ou ser removido. network_mode: host comporta-se de forma diferente num namespace de utilizador. depends_on com condition: service_healthy tem suporte inconsistente entre versões do podman-compose. restart: always não sobrevive sozinho a um reboot, o que a próxima secção resolve. O Compose continua a ser uma boa forma de descrever uma stack com vários contentores num único ficheiro e, com Podman, funciona como uma camada de tradução. Para uma stack que pretende manter durante anos, converta-a em quadlets e mantenha uma só abstração em vez de duas.
Pods: o conceito para o qual o Docker não tem resposta
Um pod é um grupo de contentores que partilham um namespace de rede. O Podman inicia um pequeno contentor infra para manter esse namespace aberto. Os membros comunicam entre si através de 127.0.0.1, sem uma rede definida pelo utilizador e sem qualquer mecanismo de descoberta de serviços.
podman pod create --name app -p 8080:80
podman run -d --pod app --name app-cache docker.io/library/redis:7
podman run -d --pod app --name app-web docker.io/library/nginx:1.27
podman pod ps
podman ps --podpodman pod ps deve mostrar o pod Running com três contentores, incluindo o contentor infra. O contentor web passa a aceder ao Redis através de 127.0.0.1:6379, em vez de app-cache:6379. Do namespace partilhado resultam duas regras: publique as portas no pod, nunca num membro, e não permita que dois membros escutem na mesma porta.
Este é o modelo do Kubernetes, e o Podman segue-o de forma consistente. podman kube generate app > app.yaml escreve um manifesto do Kubernetes a partir do que está em execução; os pacotes mais antigos usam podman generate kube. podman kube play app.yaml recria-o noutro host. O Quadlet tem um tipo de unidade .kube que executa esse ficheiro como um serviço systemd. É uma forma realmente diferente de agrupar serviços e o principal motivo para escolher o Podman se o Kubernetes fizer parte dos seus planos.
Início automático sem daemon: unidades quadlet
Quadlet é um gerador do systemd. Converte um ficheiro curto que descreve um contentor num serviço real do systemd no arranque. Os ficheiros ficam em ~/.config/containers/systemd/ para um utilizador rootless ou em /etc/containers/systemd/ para root.
~/.config/containers/systemd/caddy.container:
[Unit]
Description=Caddy web server
After=network-online.target
[Container]
Image=docker.io/library/caddy:2
ContainerName=caddy
PublishPort=8080:80
Volume=caddy-data.volume:/data
Environment=TZ=UTC
AutoUpdate=registry
[Service]
Restart=always
MemoryMax=512M
[Install]
WantedBy=default.target~/.config/containers/systemd/caddy-data.volume pode estar quase vazio, porque o cabeçalho da secção é o que cria o volume:
[Volume]systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50O nome do serviço vem do nome do ficheiro: caddy.container torna-se caddy.service. Não execute systemctl --user enable caddy. As unidades geradas não podem ser ativadas, e o systemd responde Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated.. A secção [Install] é o que inicia o contentor no arranque, e daemon-reload é o que regenera a unidade depois de editar o ficheiro.
Agora, a configuração que apanha quase toda a gente:
sudo loginctl enable-linger deploy
loginctl show-user deploy --property=LingerEspere Linger=yes. Sem linger, o systemd encerra toda a sessão do utilizador quando a última ligação SSH é fechada. Por isso, todos os contentores rootless param com ela e nenhum volta a arrancar no boot. Os contentores que desaparecem quando termina a sessão têm sempre esta causa.
Como o contentor é o processo principal de uma unidade de serviço normal, os controlos do próprio systemd aplicam-se diretamente. MemoryMax= e CPUQuota= na secção [Service] funcionam exatamente como em qualquer outro serviço que limite com o systemd. Isto requer cgroup v2 (versão 2 do grupo de controlo), que o Ubuntu utiliza por predefinição desde 22.04. Confirme com podman info | grep -i cgroup.
As atualizações têm um mecanismo correspondente. AutoUpdate=registry mais systemctl --user enable --now podman-auto-update.timer verifica no registry se existe uma imagem mais recente com a mesma tag, reinicia a unidade e reverte para a imagem anterior se o novo contentor não iniciar. Execute primeiro podman auto-update --dry-run para ver o que seria alterado. O comando podman generate systemd mais antigo ainda existe e está obsoleto, por isso escreva quadlets para tudo o que for novo.
Onde o alias docker funciona e onde não funciona
sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker pspodman-docker instala um wrapper /usr/bin/docker que chama o Podman. Sem o ficheiro nodocker, cada chamada apresenta primeiro Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg. O wrapper cobre os comandos que utiliza diariamente: run, ps, logs, exec, build, pull, push, inspect, cp, volume, network.
O que não é transferido é uma lista mais curta e mais importante. O modo Swarm não tem equivalente, por isso uma stack do Swarm não tem onde ser executada. As ferramentas que comunicam com o socket do Docker precisam que o socket do Podman seja exportado, e algumas continuam a detetar a diferença; o provider Docker do Traefik funciona quando é apontado para /run/user/<uid>/podman/podman.sock, enquanto o Watchtower não tem equivalente, porque podman auto-update desempenha essa função. O armazenamento é separado, por isso o Podman não consegue ver as imagens que já foram descarregadas com o Docker, e podman images num host Docker ocupado começa vazio.
Migrar uma stack em execução, passo a passo
- Crie ou escolha o utilizador sem privilégios que será proprietário dos containers e confirme que tem um intervalo em
/etc/subuid. - Faça novamente pull de tudo o que veio de um registry, usando nomes totalmente qualificados. O Podman tem o seu próprio armazenamento de imagens e não lê o armazenamento do Docker.
- Transfira as imagens compiladas localmente com
docker save app:1.4 | podman load. - Pare o container Docker, copie o conteúdo de cada volume para fora de
/var/lib/docker/volumes/<name>/_datae corrija depois o proprietário compodman unshare chown -R 1000:1000 <path>. - Resolva a questão das portas: publique acima de 1024 atrás de um reverse proxy ou defina
net.ipv4.ip_unprivileged_port_start. - Escreva um ficheiro quadlet por container, execute
systemctl --user daemon-reloade inicie cada serviço. - Execute
sudo loginctl enable-linger <user>, reinicie o VPS, volte a iniciar sessão e confirme quepodman pslista novamente todos os serviços.
Os dois motores não partilham nada: têm armazenamento de imagens e redes separados. Por isso, pode executar ambos durante a migração. A única coisa que pode causar conflitos é o número de uma porta do host. Migre um serviço, monitorize-o durante um dia e só depois migre o seguinte.
Podman vs Docker: qual deve ser usado no seu VPS?
Mantenha o Docker se a sua stack estiver definida em ficheiros Compose que também são mantidos por outras pessoas, ou se depender de ferramentas que comunicam com o socket do Docker. A compatibilidade com o que todos os outros escrevem é uma funcionalidade real, e o Docker oferece mais compatibilidade. Uma equipa cujos portáteis executam Docker também obtém uma vantagem concreta ao usar o mesmo motor em produção.
Mude para Podman se o VPS executar um conjunto reduzido de serviços que controla de ponta a ponta, ou se quiser que cada aplicação seja executada pelo seu próprio utilizador sem privilégios, sem qualquer grupo docker no sistema. O alinhamento com a distribuição também conta: o RHEL e os seus rebuilds distribuem o Podman como motor suportado, portanto, nesses sistemas, o Podman é a opção que causa menos surpresas. Se já supervisiona tudo o resto com unidades systemd, os quadlets parecerão uma peça em falta que finalmente chegou, e não uma nova ferramenta para aprender.
Vale a pena mencionar uma opção intermédia. O Podman rootful comporta-se de forma muito semelhante ao Docker, mantém o comando docker através do wrapper e continua a eliminar o daemon sempre em execução. Também abdica da execução rootless, que é a parte que altera a sua posição de segurança, por isso deve tratá-lo como uma etapa intermédia.
Se ainda estiver a criar o seu primeiro host de contentores, o procedimento de configuração e reforço de segurança do Docker num VPS novo é o caminho mais curto, e nenhum desse conhecimento será desperdiçado. As imagens e os volumes são os mesmos objetos nos dois motores, portanto, uma migração altera a forma como os serviços são supervisionados e pouco mais.
FAQ
O Podman é um substituto direto do Docker?
Para os comandos que introduz, é quase isso. A instalação de podman-docker disponibiliza um wrapper /usr/bin/docker, e run, ps, build, logs e exec funcionam da mesma forma. Não substitui o daemon. O Swarm não tem equivalente, as ferramentas que se ligam a /var/run/docker.sock têm de apontar para o socket do Podman por utilizador e as imagens obtidas pelo Docker permanecem invisíveis para o Podman, porque os dois usam armazenamentos separados.
Porque é que os meus contentores rootless do Podman param quando termino a sessão SSH?
Porque o systemd para a sessão do utilizador e, com ela, todos os serviços desse utilizador quando o último login é encerrado. Execute sudo loginctl enable-linger <user> e confirme depois que loginctl show-user <user> --property=Linger apresenta Linger=yes. O linger mantém a instância do systemd desse utilizador em execução sem uma sessão ativa. É também isso que permite aos contentores arrancar novamente depois de um reboot.
Porque é que os ficheiros no meu volume pertencem ao UID 100999?
O Podman rootless mapeia o UID 0 do contentor para o seu utilizador no host. Depois, mapeia o UID 1 do contentor e os seguintes para o seu intervalo de subuid. Com um intervalo que começa em 100000, o UID 1000 do contentor torna-se 100999 no host. Corrija-o a partir do namespace com podman unshare chown 1000:1000 /path/to/data, monte com a flag :U na primeira execução ou use --userns=keep-id para que os UIDs dos contentores correspondam aos seus.
Posso continuar a usar docker-compose.yml com o Podman?
Sim, de duas formas. podman-compose lê o ficheiro e controla diretamente a CLI do Podman. Em alternativa, ative o socket de compatibilidade com systemctl --user enable --now podman.socket, defina DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock e execute o docker compose real através dele. Espere limitações em network_mode: host, nos serviços que montam o socket do Docker e em restart: always, que precisa de uma unidade quadlet e de linger para sobreviver a um reboot.
O rootless torna realmente os contentores mais seguros?
Remove um risco específico: um processo que escape de um contentor rootless mantém as permissões do seu utilizador sem privilégios, em vez das permissões de root. Isso é importante e explica por que razão o grupo docker, equivalente a root, não tem correspondente no Podman rootless. Não impede vulnerabilidades do kernel nem protege os ficheiros que o seu próprio utilizador pode ler. Por isso, mantenha as restantes medidas de hardening que aplicaria em qualquer servidor.