Podman vs Docker em VPS: o que realmente muda
Podman não usa daemon e roda rootless por padrão. Veja o impacto em Compose, Quadlets, portas abaixo de 1024 e permissões 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 com root que controla todos os contentores, e o comando docker é um cliente pequeno que pede a esse daemon para executar o trabalho. O Podman não tem daemon: podman run inicia o contentor como processo filho do processo que o chamou, com 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 namespace de utilizadores, por isso o proprietário apresentado por ls -l no host não é o proprietário que o contentor vê. As portas inferiores a 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 precisa do socket do Docker.
Nenhum daemon: o que realmente é executado quando 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 é um processo filho desse shim, e o shim é um processo 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 e, com a opçã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 processo conmon (monitor de container) 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, e curl deve mostrar 200. Como nenhum serviço central é proprietário do container, sudo apt upgrade podman não para nada que já esteja em execução, e a falha do monitor de um container não pode afetar os outros.
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 mantida pelo daemon no arranque, e o Podman substitui-a 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 (application programming interface) pertencente a root, e qualquer processo que possa escrever nele pode iniciar um container privilegiado que monta o sistema de ficheiros do host. Adicionar um utilizador ao grupo docker concede root a esse utilizador por uma via mais lenta. Vale a pena ler isto em conjunto com conceder a cada conta de serviço apenas o acesso de que precisa. O Podman não expõe nenhum socket, a menos que o solicite, e 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á realmente 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 comum utilizar um intervalo de IDs subordinados. Sem eles, os contentores rootless não arrancam. podman info deve apresentar rootless: true.
O Ubuntu 24.04 disponibiliza o Podman 4.9 e o Debian 13 disponibiliza 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 o recebe, 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, em seguida, reinicialize o armazenamento desse utilizador para utilizar 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 proporcionam num servidor alugado
Um contentor rootless é executado dentro de um namespace de utilizador, 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 normal de login. O root no contentor não é root no host.
Esse é o verdadeiro alcance do benefício. Uma imagem que exige ser executada como root, uma aplicação Web com uma vulnerabilidade de execução remota de código ou uma fuga que dependa de UID 0 fora do namespace 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 e também não protege os seus próprios ficheiros, porque o processo que escapou é executado como você e pode ler tudo o que você pode ler. O isolamento da unidade é pelo menos tão importante como o mapeamento de UID. Isto fica mais claro no exemplo seguinte, em um jail FreeBSD, que encapsula todo um userland que administra como uma pequena máquina, em vez de uma imagem em camadas obtida de um registry.
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 orientação predefinida. Com o Podman, obtém rootless sem ter de o pedir. Assim, a sua 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 volume pertencem ao UID 100999?
Isso acontece por causa do mesmo namespace de utilizadores. O UID 0 do contentor é mapeado para o UID do host. O UID 1 do contentor é mapeado para o primeiro ID do seu intervalo de subuid, e os valores seguintes são incrementados a partir daí. Com um intervalo que começa 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 no host apresenta o proprietário 100999, porque 100000 mais 1000 menos 1 é 100999. Não há nada avariado. Um chown simples também não resolve o problema, porque o seu utilizador sem privilégios não pode alterar a propriedade de 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 que queira preservar.--userns=keep-idmapeia o seu UID do host para o mesmo UID dentro do contentor, para que os ficheiros novos pertençam a si.- 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, é o mesmo problema, mas uma camada acima. As variáveis PUID e PGID que muitas imagens disponibilizam definem o UID usado pelo processo dentro do contentor. No Podman rootless, esse UID é mapeado novamente. PUID=1000 dentro de um contentor rootless continua a criar ficheiros no host pertencentes ao UID 100999. Escolha os números tendo esse segundo mapeamento em conta 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. O Ubuntu usa AppArmor, por isso essas flags não têm efeito nesse sistema. O Podman rootless também não pode montar um diretório do host que o seu utilizador não consiga ler. Esse é o comportamento esperado, não uma falha.
Por que o Podman rootless recusa publicar a porta 80?
Porque associar uma porta abaixo de 1024 requer 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 efeito desta definição: todos os utilizadores da máquina passam a poder associar-se às portas 80 e 443, e não apenas o utilizador que executa os contentores. Num VPS administrado por uma única pessoa, essa pode ser uma troca 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 rootless também altera o que a aplicação vê. O Podman 4.x usa slirp4netns com o processador 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. Esta opção preserva o endereço real do cliente. No 4.x, --network slirp4netns:port_handler=slirp4netns repõe o endereço de origem verdadeiro, com algum impacto no débito.
Há aqui uma consequência positiva. Uma porta publicada em modo rootless é um socket de escuta normal, pertencente a um processo normal. Por isso, as regras de entrada da firewall aplicam-se a ela. O Docker publica portas escrevendo regras NAT (network address translation) e regras próprias de aceitação do encaminhamento. É exatamente por isso que uma porta publicada pelo Docker ignora a regra do ufw que pensava estar a bloqueá-la. O Podman rootful usa uma configuração semelhante e herda a mesma armadilha. O modo rootless não.
Os meus ficheiros Docker Compose continuam a funcionar com Podman?
Na maioria dos casos, 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 consiste no 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 algumas limitações reais. Tudo o que montar /var/run/docker.sock tem de ser apontado para o socket do Podman ou removido. network_mode: host comporta-se de forma diferente num namespace de utilizador. depends_on com condition: service_healthy tem suporte irregular entre as diferentes 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 para quadlets e mantenha uma única abstração em vez de duas.
Pods: a ideia para a qual o Docker não tem resposta
Um pod é um grupo de contentores que partilham o mesmo namespace de rede. O Podman inicia um pequeno contentor infra para manter esse namespace aberto, e os membros comunicam entre si através de 127.0.0.1, sem uma rede definida pelo utilizador e sem 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 de infraestrutura. O contentor web passa a aceder ao Redis em 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 nenhum membro pode escutar na mesma porta que outro.
Este é o modelo do Kubernetes, e o Podman segue-o diretamente. 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), e 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.
Inicialização automática sem daemon: unidades quadlet
Quadlet é um gerador do systemd. Ele transforma 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 ficar 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. Não é possível ativar unidades geradas, 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 definiçã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 a sessão e nenhum volta a arrancar no boot. Os contentores que desaparecem quando termina a sessão têm sempre este problema.
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 funcionam para qualquer outro serviço que controle 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 juntamente com systemctl --user enable --now podman-auto-update.timer verifica se existe uma imagem mais recente no registry com a mesma tag, reinicia a unidade e reverte para a imagem anterior se o novo contentor não iniciar. Execute podman auto-update --dry-run primeiro para ver o que seria alterado. O comando podman generate systemd antigo continua disponível e está obsoleto, por isso utilize 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 abrange 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 restritiva. O modo Swarm não tem equivalente, por isso uma stack Swarm não tem onde ser executada. As ferramentas que comunicam com o socket Docker precisam que o socket 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 utilidade, porque podman auto-update assume essa função. O armazenamento é separado, por isso o Podman não consegue ver as imagens que já obteve com o Docker, e podman images num host Docker ocupado começa vazio.
Migração de uma stack em execução, passo a passo
- Crie ou escolha o utilizador sem privilégios que será proprietário dos contentores e confirme que tem um intervalo em
/etc/subuid. - Volte a obter 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 criadas localmente com
docker save app:1.4 | podman load. - Pare o contentor Docker, copie o conteúdo de cada volume para fora de
/var/lib/docker/volumes/<name>/_datae corrija depois a propriedade compodman unshare chown -R 1000:1000 <path>. - Resolva a questão das portas: publique acima de 1024 atrás de um reverse proxy ou configure
net.ipv4.ip_unprivileged_port_start. - Escreva um ficheiro quadlet por contentor, 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. O único recurso pelo qual podem entrar em conflito é o número de uma porta do host. Migre um serviço, monitorize-o durante um dia e migre depois o seguinte.
Podman vs Docker: qual deles deve estar no seu VPS?
Continue com Docker se sua stack estiver em arquivos Compose que também são mantidos por outras pessoas ou se você depender de ferramentas que se comunicam com o socket do Docker. A compatibilidade com o que todo mundo escreve é um recurso real, e o Docker oferece mais compatibilidade. Uma equipe cujos notebooks executam Docker também obtém uma vantagem concreta ao usar o mesmo engine em produção.
Mude para Podman se o VPS executar poucos serviços que você controla de ponta a ponta ou se quiser cada aplicação sob seu próprio usuário sem privilégios, sem nenhum grupo docker no host. O alinhamento com a distribuição também conta: RHEL e suas reconstruções distribuem o Podman como engine compatível oficialmente. Nesses sistemas, o Podman é o caminho com menos surpresas. Se você quiser usar Docker mesmo assim nesses hosts, o caminho via dnf no Rocky Linux e no AlmaLinux começa removendo o wrapper podman-docker, que já controla o comando docker nesses sistemas. Se você já supervisiona todo o restante com unidades do systemd, os quadlets parecerão uma peça que estava faltando, e não uma nova ferramenta para aprender.
Uma opção intermediária também merece ser mencionada. O Podman rootful se comporta de forma muito semelhante ao Docker, mantém o comando docker por meio do wrapper e ainda remove o daemon sempre em execução. Ele também elimina a parte rootless, que é o componente que altera sua postura de segurança. Portanto, trate essa opção como uma etapa intermediária.
Se você ainda estiver montando seu primeiro host de contêineres, o caminho de configuração e hardening do Docker em um VPS novo será o percurso mais curto, e nenhum desse conhecimento será desperdiçado. Imagens e volumes são os mesmos objetos nos dois engines. Portanto, uma migração posterior altera a forma como seus serviços são supervisionados e quase nada mais.
FAQ
O Podman é um substituto direto do Docker?
Para os comandos que introduz, é praticamente isso. A instalação de podman-docker fornece um wrapper /usr/bin/docker, e run, ps, build, logs e exec funcionam da mesma forma. Não é um substituto do 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 continuam invisíveis para o Podman, porque os dois mantêm armazenamento separado.
Porque é que os meus contentores Podman rootless 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 termina o último login. 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 e os seguintes do contentor para o intervalo de subuid desse utilizador. Com um intervalo iniciado em 100000, o UID 1000 do contentor torna-se 100999 no host. Corrija-o a partir do interior do namespace com podman unshare chown 1000:1000 /path/to/data, monte o volume com a flag :U na primeira execução ou use --userns=keep-id para que os UIDs do contentor 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 fica com as permissões do seu utilizador sem privilégios, e não com as permissões de root. Isso é importante e explica por que razão o grupo equivalente a root docker não tem equivalente 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.