Introdução
Podman rootless em produção deixou de ser uma curiosidade técnica e virou uma exigência prática em ambientes que precisam atender a controles de compliance como CIS Benchmarks, PCI-DSS e LGPD. Quando rodo containers Docker em um servidor, o dockerd precisa de um socket privilegiado e qualquer usuário no grupo docker é, na prática, root na máquina. Em auditorias internas eu vi esse ponto barrar deploys inteiros, e a resposta mais limpa que encontrei nos últimos dois anos foi migrar para Podman rodando sem daemon e sem privilégios.
A tese deste artigo é direta: Podman rootless substitui Docker em produção sem perda funcional na maioria dos cenários web e de workers, desde que você aceite três trocas explícitas — usar systemd (via Quadlet) no lugar de docker compose, mapear UIDs com subuid/subgid, e tratar redes e volumes pensando em namespaces de usuário. Em troca, o servidor passa a ser auditável: nenhum processo de container roda como root no host, o blast radius de uma RCE cai drasticamente e a superfície de ataque do daemon simplesmente some.
Vou mostrar como saí de um host Docker tradicional para um host Podman 5.x rootless rodando workloads .NET, n8n e Postgres em produção, qual é a estrutura de diretórios que sobreviveu a três meses de operação, como fica o pipeline de deploy via SSH e quais armadilhas custam horas se você não souber onde olhar (porta 80, linger, pasta networking, SELinux). Tudo testado em Ubuntu 24.04 LTS e RHEL 9 com crun como runtime padrão.
Pré-requisitos
- Linux com kernel ≥ 5.13 (Ubuntu 22.04+, Debian 12+, RHEL 9+)
- Podman ≥ 4.4 (para suporte completo a Quadlet); idealmente 5.x
- Usuário não-root com
subuid/subgidconfigurados -
systemdem modo user habilitado (loginctl enable-linger <user>) - Conhecimento básico de
Dockerfileedocker compose
Por Que Rootless Não É “Docker com Outro Nome”
A confusão mais comum que encontro é tratar Podman como um drop-in de Docker apenas porque alias docker=podman funciona. Funciona para run, build e pull, mas o modelo de execução é radicalmente diferente.
Docker depende de um daemon (dockerd) que escuta em /var/run/docker.sock rodando como root. Esse daemon é quem executa os containers. Se um atacante consegue qualquer escrita no socket, ele consegue um container --privileged montando / do host — game over. É por isso que CIS Docker Benchmark 1.2.x dedica uma seção inteira ao socket.
Podman é daemonless. Cada podman run é um processo filho do seu shell (ou do systemd --user). Não existe socket privilegiado por padrão. Em modo rootless, o container roda dentro de um user namespace mapeado via subuid/subgid: o UID 0 dentro do container é, na verdade, um UID alto e sem privilégios no host (ex: 100000). Se o processo escapa do container, ele aterrissa como um usuário comum sem permissão de escrita em quase nada.
ℹ️ Informação: Em testes do Red Hat com CVE-2019-5736 (escape clássico do
runc), o exploit em Docker rootful conseguia sobrescrever o binário doruncno host. Em Podman rootless o mesmo exploit aterrissa como UID 100000 sem permissão de escrita — o container “escapa”, mas não causa dano.
Essa diferença muda como você pensa em backups, logs e orquestração. Logs de container rootless vão para o journal do usuário, não para /var/log/. Backups de volumes ficam em ~/.local/share/containers/storage/volumes/. E systemctl restart precisa do flag --user.
Configurando o Host: subuid, subgid e Linger
A configuração base do host é o ponto onde 80% dos problemas aparecem em produção. Sem ela, o container até sobe, mas reinicia depois do logout, perde acesso a portas, ou falha em montar volumes com o erro confuso lchown: operation not permitted.
# Criar usuário dedicado para a aplicação
sudo useradd -m -s /bin/bash app
# Reservar 65536 UIDs e GIDs subordinados para o usuário 'app'
# Sem isso, podman rootless falha ao mapear o UID do container
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 app
# Habilitar systemd --user mesmo sem sessão SSH ativa
# Essencial para containers em produção que precisam sobreviver ao logout
sudo loginctl enable-linger app
# Permitir bind em portas baixas (80/443) sem CAP_NET_BIND_SERVICE
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system
O loginctl enable-linger é o comando que mais esqueço — sem ele, qualquer systemctl --user start morre quando a sessão SSH cai. A linha ip_unprivileged_port_start=80 evita ter que recorrer a setcap ou proxy reverso só para servir HTTP.
⚠️ Atenção: Se você reaproveitar UIDs subordinados que já estão alocados a outro usuário, o Podman vai recusar o
runcomIDs already in use. Sempre rodecat /etc/subuid /etc/subgidantes de adicionar ranges.
Quadlet: o Substituto Real do docker-compose
docker-compose foi a peça mais difícil de substituir na minha migração. podman-compose existe, mas é mantido pela comunidade e tem inconsistências em redes e healthchecks. A solução oficial é Quadlet, integrado ao Podman 4.4+.
Quadlet permite descrever containers, redes e volumes em arquivos .container, .network e .volume que o systemd traduz automaticamente em units. Isso significa: restart policies, dependências (After=, Requires=), logs no journal, healthchecks supervisionados pelo systemd. Tudo grátis, sem dockerd.
A localização para units rootless é ~/.config/containers/systemd/. O systemd --user faz parsing toda vez que você roda systemctl --user daemon-reload.
# ~/.config/containers/systemd/api.container
[Unit]
Description=API .NET 9 em Podman rootless
After=network-online.target
[Container]
Image=ghcr.io/lincoln/api:1.4.0
AutoUpdate=registry
ContainerName=api
PublishPort=8080:8080
Environment=ASPNETCORE_ENVIRONMENT=Production
Volume=api-data.volume:/data:Z
Network=apps.network
HealthCmd=curl -f http://localhost:8080/health || exit 1
HealthInterval=30s
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.target
Após criar o arquivo, basta systemctl --user daemon-reload && systemctl --user start api.service. O systemd cria a unit, baixa a imagem, sobe o container e monitora o healthcheck.
Migrando docker-compose.yml para Quadlet
Para projetos com vários serviços, traduzir manualmente é viável, mas há atalhos. A ferramenta podlet converte um docker-compose.yml em arquivos Quadlet em segundos:
# Instalar podlet (binário Go, sem dependências)
curl -L -o /tmp/podlet.tar.gz \
https://github.com/containers/podlet/releases/latest/download/podlet-x86_64-unknown-linux-gnu.tar.gz
tar -xzf /tmp/podlet.tar.gz -C ~/.local/bin/
# Converter compose existente para Quadlet
podlet --file ~/.config/containers/systemd/ compose docker-compose.yml
A conversão acerta volumes, redes, dependências e portas em ~90% dos casos. Os 10% restantes geralmente são depends_on com condition: service_healthy (Quadlet usa Requires= + After=) e extra_hosts (que vira AddHost= no bloco [Container]).
💡 Dica: Mantenha o
docker-compose.ymloriginal no repositório por enquanto, com um README apontando para os arquivos Quadlet. Devs locais continuam usando Docker Desktop; o servidor de produção usa Podman/Quadlet. Os dois descrevem a mesma topologia.
Rede, Volumes e SELinux: As Três Pegadinhas
Rede em modo rootless
Por padrão, Podman rootless usa pasta (em versões 5.x) ou slirp4netns (versões anteriores) como backend de rede. Funciona, mas tem limitações: o IP de origem do tráfego visto pelo container é sempre 10.0.2.100 (com slirp4netns), o que quebra rate-limiting e logs de auditoria baseados em IP. Com pasta, o IP real é preservado, e por isso eu sempre forço Podman 5.x em produção nova.
Volumes e o flag :Z
Em hosts com SELinux (RHEL, Fedora, Rocky), montar um volume sem o flag :Z no Quadlet ou -v ./data:/data:Z no run causa Permission denied silencioso dentro do container. O :Z aplica a label SELinux correta no diretório do host. Sem ele, debugging vira pesadelo porque ls -l mostra permissões corretas, mas o processo do container não consegue ler.
Mapeamento de UID em volumes
Quando o container escreve num volume bind-mounted, o arquivo no host aparece com UID 100000 (o UID subordinado mapeado). Para sincronizar com seu usuário local, use podman unshare:
# Entrar no namespace de usuário do podman
podman unshare chown -R 1000:1000 /home/app/data
# Equivalente a "chown 100999:100999" visto do host
Configuração Otimizada: containers.conf, registries.conf, storage.conf
Os defaults do Podman funcionam, mas em produção rootless há três arquivos que decidem performance e segurança operacional. Em modo rootless, ficam em ~/.config/containers/. Esta é a configuração que rodo num RHEL 9.7 com disco dedicado para /userapps e SSD limitado em IOPS — ela elimina escritas temporárias massivas e usa journald em RAM como sink de log.
containers.conf — runtime e logging
# ~/.config/containers/containers.conf
# Referência: man 5 containers.conf
[containers]
tz = "America/Sao_Paulo"
# Driver de log: journald armazena em RAM via journal, não em arquivo no disco
log_driver = "journald"
log_size_max = 10485760 # 10 MB por container
# Limite de PIDs por container (mitigação de fork bomb)
pids_limit = 2048
[engine]
# TMPDIR em /dev/shm (tmpfs em RAM) elimina milhões de writes temporários no disco
env = ["TMPDIR=/dev/shm/podman-tmp"]
# Eventos do Podman vão para journald, não para arquivos no disco
events_logger = "journald"
[network]
# Netavark é o backend moderno (substitui CNI desde Podman 4.x)
network_backend = "netavark"
network_config_dir = "/userapps/zocateli/containers/networks"
O ganho prático mais visível é o TMPDIR=/dev/shm/...: em servidor com muitos podman pull e podman build, o disco recebia milhões de arquivos temporários que sumiam segundos depois. Movendo para tmpfs, o I/O do disco caiu drasticamente sem perder funcionalidade.
registries.conf — anti supply-chain
# ~/.config/containers/registries.conf
[registries.search]
registries = ['meu-docker.artifacts.zocate.li']
# Recusa qualquer "podman run nginx" sem prefixo de registry
short-name-mode = "enforcing"
O short-name-mode = "enforcing" é um controle subestimado: sem ele, podman run nginx pode resolver para qualquer registry da lista de busca, abrindo brecha para supply-chain attack. Com enforcing, o usuário precisa especificar o registry completo (docker.io/library/nginx) — força que toda imagem seja explicitamente atribuída a uma fonte.
storage.conf — overlay nativo (60–80% menos I/O)
# ~/.config/containers/storage.conf
# Referência: man 5 containers-storage.conf
[storage]
driver = "overlay"
# Storage persistente (imagens, layers) → disco dedicado
graphroot = "/userapps/zocateli/containers/storage"
rootless_storage_path = "/userapps/zocateli/containers/storage"
# runroot (PIDs, sockets, locks) é resolvido automaticamente via $XDG_RUNTIME_DIR
# que aponta para /run/user/<UID>/containers (tmpfs em RAM) — não definir aqui
[storage.options]
pull_options = {enable_partial_images = "false", use_hard_links = "false"}
[storage.options.overlay]
# SEM mount_program → Podman usa overlay NATIVO do kernel (5.11+)
# Habilitar fuse-overlayfs apenas se overlay nativo não funcionar:
# mount_program = "/usr/bin/fuse-overlayfs"
# SEM metacopy=on → evita copy-up overhead em workloads write-heavy
mountopt = "nodev"
O ponto crítico é a ausência de mount_program. Em kernel 5.11+ (RHEL 9, Ubuntu 22.04+, Fedora 35+), o overlay nativo suporta rootless sem fuse-overlayfs. Cortar o FUSE elimina syscalls de userspace para cada operação de filesystem — em workloads I/O-bound, vejo 60–80% de redução no tempo de operações de layer (build, pull, run de imagens grandes).
⚠️ Atenção: Se o backing filesystem for ext4 sem suporte a overlay rootless, ou kernel < 5.11, descomente
mount_program = "/usr/bin/fuse-overlayfs". Sem essa fallback, opodman pullvai falhar commount: permission denied. Para detectar suporte:podman info | grep graphDriverNamedeve mostraroverlay, nãovfs.
Exemplo Prático: API .NET + Postgres + Worker em Produção
Um caso real que rodo hoje: API .NET 9, worker de processamento e Postgres 17, todos em Podman rootless, supervisionados por systemd --user. Estrutura:
~/.config/containers/systemd/
├── apps.network
├── postgres.volume
├── postgres.container
├── api.container
└── worker.container
# postgres.container — banco persistente
[Unit]
Description=Postgres 17
After=network-online.target
[Container]
Image=docker.io/library/postgres:17-alpine
ContainerName=postgres
Environment=POSTGRES_PASSWORD_FILE=/run/secrets/pg_pass
Secret=pg_pass,type=mount,target=pg_pass
Volume=postgres.volume:/var/lib/postgresql/data:Z
Network=apps.network
HealthCmd=pg_isready -U postgres
HealthInterval=10s
[Service]
Restart=always
[Install]
WantedBy=default.target
# api.container — depende do Postgres saudável
[Unit]
Description=API .NET 9
Requires=postgres.service
After=postgres.service
[Container]
Image=ghcr.io/lincoln/api:1.4.0
AutoUpdate=registry
ContainerName=api
PublishPort=8080:8080
Environment=ConnectionStrings__Db=Host=postgres;Username=postgres;Password=...
Network=apps.network
[Service]
Restart=always
[Install]
WantedBy=default.target
Deploy é um git pull && systemctl --user restart api.service. Sem daemon, sem sudo, sem socket aberto. O update da imagem é orquestrado por podman auto-update (habilitado com AutoUpdate=registry no Quadlet) que checa registry diariamente e reinicia containers automaticamente em caso de nova tag.
📂 Código Fonte: Os manifests Quadlet completos e o Containerfile do worker estão no repositório de exemplos do blog:
BlogSamples/Workers/
Auto-Update na Prática: Quadlet .image, Timer e o Que .network/.volume Não Fazem
O podman auto-update é disparado pelo timer podman-auto-update.timer do systemd --user (diário por padrão). Ele varre todas as units com a label io.containers.autoupdate e:
- Faz
skopeo inspectno registry para comparar o digest atual com o publicado. - Se mudou, faz
podman pullda nova imagem. - Reinicia o serviço systemd correspondente.
- Se o restart falhar (healthcheck negativo), faz rollback automático para a imagem anterior. Habilitar o timer:
systemctl --user enable --now podman-auto-update.timer
systemctl --user list-timers | grep auto-update
Mudando a cadência para 5 em 5 minutos
O timer default roda uma vez por dia (OnCalendar=daily + RandomizedDelaySec=900). Para deploys mais agressivos, dá para baixar para 5 minutos sem editar arquivos do pacote — basta criar um override do systemd user:
systemctl --user edit podman-auto-update.timer
Adicione apenas:
# ~/.config/systemd/user/podman-auto-update.timer.d/override.conf
[Timer]
# Reset OnCalendar herdado do timer original
OnCalendar=
OnCalendar=*:0/5
# Sem janela aleatória — queremos previsibilidade no deploy
RandomizedDelaySec=0
# Recupera execuções perdidas (ex: máquina hibernando)
Persistent=true
Recarregue e confirme:
systemctl --user daemon-reload
systemctl --user restart podman-auto-update.timer
systemctl --user list-timers podman-auto-update.timer
⚠️ Atenção: Os dois
OnCalendar=são intencionais. A primeira linha vazia limpa o valor herdado do timer original (daily). Sem ela, o timer dispara nas duas cadências — diariamente e a cada 5 minutos. Esse é o padrão de override de timers do systemd documentado emsystemd.timer(5).
Tradeoffs com 15 containers no mesmo host
Reduzir o intervalo de 1 dia para 5 minutos significa multiplicar o número de execuções por 288×. Para 15 containers compartilhando o mesmo host, vale revisar quatro pontos antes de decidir:
| Eixo | 1× por dia | 1× a cada 5 minutos (15 containers) |
|---|---|---|
| Requisições ao registry | 15/dia | 4.320/dia |
| Tráfego se nada mudou | ~3 KB/inspect × 15 | ~12 MB/dia (manifest HEAD + JSON) |
| Restart simultâneo | 1 janela/dia | 288 janelas/dia |
| Janela de exposição a CVE | até 24h | até 5 min |
1. Rate limit do registry. Docker Hub anônimo: 100 pulls/6h por IP. Cada podman auto-update faz skopeo inspect (não conta como pull, mas conta como request) — 15 containers × 12 verificações/h = 180 req/h. Em GHCR autenticado o limite é 5.000 req/h, sem dor. Em registry interno, geralmente irrelevante. Em Docker Hub anônimo com muitos containers, vai bater rate limit.
2. Tempestade de restart simultâneo. Quando um digest novo é publicado e múltiplos containers da stack dependem dele (ex: API + worker compartilhando a mesma base image), o auto-update reinicia todos juntos. Se o healthcheck demora 30s e há Requires= em cadeia, a stack inteira pode ficar 1–2 minutos degradada — só que agora 288 vezes por dia em vez de 1. Sem load balancer com drain, isso é downtime visível.
3. CPU/IO no host. Cada execução é leve isoladamente (~50ms por container, sem mudança). Para 15 containers a cada 5 min, o overhead agregado fica em torno de 0,3% de CPU constante e ~10 MB extras de tráfego de log no journald. Aceitável em produção, mas em hosts com muitos containers (50+), considere RandomizedDelaySec=60 para evitar picos coincidentes.
4. Risco de rollback em cascata. O auto-update faz rollback por container, não atômico para a stack. Se o digest novo da imagem da API quebra o contrato com o worker (que ainda está na versão antiga), 5 minutos depois o worker também pega a nova versão e quebra junto. Em janela diária dá tempo de detectar; em janela de 5 minutos, não.
💡 Dica: Para 15 containers em produção real eu uso 15 minutos (
OnCalendar=*:0/15) comRandomizedDelaySec=300, e ainda assim só em containers que cumprem dois critérios: tag semver imutável + healthcheck rápido (< 10s) + zero dependência de outro container. Para o resto, mantenho cadência diária e faço deploy explícito via CI quando preciso de janela menor. A cadência de 5 minutos faz sentido em pré-produção/canary, raramente em produção heterogênea.
A chave AutoUpdate= aceita dois valores no bloco [Container]:
| Valor | Quando usar |
|---|---|
| registry | Pull do registry remoto. Tag precisa ser versão imutável (semver, calendar ou digest). |
| local | Imagem foi rebuilt localmente (podman build). Compara digest local. |
Quadlet .image — separar pull do start
Quando várias .container units usam a mesma imagem (ex: API + sidecar de migration sharing a mesma base), vale extrair o pull para um arquivo .image dedicado. O auto-update opera no arquivo de imagem e os containers consomem por referência:
# ~/.config/containers/systemd/api-image.image
[Image]
Image=ghcr.io/lincoln/api:1.4.0
AutoUpdate=registry
# api.container — consome a image unit
[Container]
Image=api-image.image # referência, não duplica a definição
.network e .volume não suportam AutoUpdate=
Essa é a confusão mais comum: a chave AutoUpdate= não existe em .network nem .volume. Eles não fazem pull de nada — são recursos estáticos do Podman, reconciliados quando você edita o arquivo e roda systemctl --user daemon-reload seguido do restart do serviço que os usa.
Um exemplo concreto migrando o serviço cloudbeaver do meu docker-compose.yml (parte da stack de dev do blog) para Quadlet:
# Antes — docker-compose.yml
cloudbeaver:
image: lzocateli/dbeaver:25.2.0
ports:
- "8978:8978"
volumes:
- C:/Users/lzoca/cloudbeaver:/opt/cloudbeaver/workspace
networks:
- lzo
depends_on:
postgres:
condition: service_healthy
Vira três arquivos Quadlet, cada um com sua responsabilidade:
# ~/.config/containers/systemd/lzo.network
[Network]
NetworkName=lzo
Driver=bridge
Label=app=blog-stack
# Sem AutoUpdate — rede é reconciliada via daemon-reload
# ~/.config/containers/systemd/cloudbeaver-workspace.volume
[Volume]
VolumeName=cloudbeaver-workspace
Label=app=blog-stack
# Bind para diretório no host (equivalente ao volume do docker-compose)
# Em Linux/WSL2 esse path é /mnt/c/Users/lzoca/cloudbeaver
Driver=local
Options=type=none,o=bind
Device=C:/Users/lzoca/cloudbeaver
# Sem AutoUpdate — volume é reconciliado via daemon-reload
# ~/.config/containers/systemd/cloudbeaver.container
[Unit]
Description=CloudBeaver — gerenciador de banco
Requires=postgres.service
After=postgres.service
[Container]
Image=docker.io/lzocateli/dbeaver:25.2.0
AutoUpdate=registry # ← única unit que faz pull
ContainerName=cloudbeaver
PublishPort=8978:8978
Environment=TZ=America/Sao_Paulo
Volume=cloudbeaver-workspace.volume:/opt/cloudbeaver/workspace:Z
Network=lzo.network
HealthCmd=curl -f http://localhost:8978/ || exit 1
HealthInterval=30s
[Service]
Restart=unless-stopped
[Install]
WantedBy=default.target
Quando o cloudbeaver recebe nova versão no Docker Hub (lzocateli/dbeaver:25.2.0 republicada com novo digest), o timer faz pull, restart e healthcheck — o lzo.network e o cloudbeaver-workspace.volume continuam intocados. Se eu editar o lzo.network para mudar driver, preciso systemctl --user daemon-reload && systemctl --user restart cloudbeaver.service manualmente — não existe “auto-update de network”.
Rollback manual
Quando o auto-update sobe uma versão quebrada e o healthcheck demora a falhar:
podman auto-update rollback # volta para a imagem anterior em todas as units
systemctl --user restart cloudbeaver.service
💡 Dica: Para máxima reprodutibilidade, troque a tag por digest pinning no
.image:Image=docker.io/lzocateli/dbeaver@sha256:abc…. O auto-update continua funcionando (ele compara digest publicado vs digest atual) e você ganha imutabilidade garantida pelo conteúdo da imagem, não pelo contrato da tag.
Compliance: O Que Auditoria Realmente Olha
O argumento de venda interno mais forte do Podman rootless não é técnico — é regulatório. Em três auditorias que acompanhei nos últimos 18 meses, os pontos atendidos automaticamente foram:
- CIS Docker Benchmark 5.x — controles de “non-root user inside container” e “no privileged containers” passam por construção
- PCI-DSS 4.0 req. 7.2.5 — least privilege para componentes de sistema é satisfeito porque o container nunca tem privilégio no host
- NIST SP 800-190 seção 4.2 — isolamento de container via user namespaces é recomendação explícita
-
ISO 27001 A.8.2 — controle de acesso privilegiado fica trivial de evidenciar quando não há grupo
dockerna máquina A evidência prática que as auditorias pedem é simples:ps -ef | grep -v podman | grep <container-pid>mostrando processo rodando com UID alto, egetent group dockerretornando vazio.
Dicas e Boas Práticas
-
Sempre habilitar
lingerantes de criar unitssystemd --user. Sem isso, todos os containers morrem no primeiro logout e você só descobre na próxima manhã quando o monitoramento gritar. -
Usar Podman 5.x para ter
pastacomo backend de rede padrão. O ganho de preservar IP de origem evita reescrever regras de rate-limit, WAF e logs. -
Versionar os arquivos Quadlet em Git junto do código da aplicação. Tratar
*.containercomo infra-as-code: pull request, code review e CI validando sintaxe viaquadlet -dryrun. -
Habilitar
AutoUpdate=registryapenas com tags versão imutáveis. Default: semver (:1.4.0,:2.0.1) — alinha com SemVer 2.0, encaixa em SBOM/SCA e é o que a maioria dos registries de mercado já publica. Variante: calendar versioning (:v2026.05.10-sha) para apps internas com release diário. Gold standard: digest pinning (Image=ghcr.io/lincoln/api@sha256:abc…) — imutabilidade garantida pelo conteúdo, não pelo contrato. Nunca:latestcom auto-update: é convite para incidente. -
Centralizar logs com
journald+vectoroupromtail. Logs rootless ficam emjournalctl --user -u <service>. Encaminhar para Loki/Elastic é trivial e mais barato quedocker logging driver. -
Rodar
podman system resetem homologação semanalmente. Limpa storage corrompido, volumes órfãos e overlay layers obsoletos. Fica óbvio quando alguma unit está com state inconsistente.
Resumo Objetivo
- Podman rootless elimina o daemon root e o socket privilegiado do Docker, reduzindo o blast radius de uma RCE em container para o de um usuário comum no host.
-
Quadlet é a substituição oficial para
docker-composeem produção: arquivos.container/.network/.volumeem~/.config/containers/systemd/viram units dosystemd --userautomaticamente. -
subuid/subgid+loginctl enable-lingersão pré-requisitos não-negociáveis: sem eles o container não mapeia UIDs ou morre no primeiro logout. -
Backend
pasta(Podman 5.x) preserva o IP de origem do cliente, ao contrário doslirp4netnsque mascara tudo como10.0.2.100. -
Flag
:Zem volumes aplica label SELinux correta em hosts RHEL/Fedora; sem ele, container falha em ler/escrever sem mensagem clara. - CIS Docker Benchmark, PCI-DSS 4.0 e NIST SP 800-190 têm controles satisfeitos automaticamente por Podman rootless que exigem trabalho manual em Docker.
-
podman auto-updatecom tags versão imutáveis (semver:1.4.0, calendar:v2026.05.10-shaou digest@sha256:…) habilita atualização contínua sem CI, supervisionada por timer dosystemd. As chavesAutoUpdate=registry|localsó existem no bloco[Container]ou em arquivos.image— não em.networkou.volume. -
podlet compose docker-compose.ymlconverte ~90% dos manifests Compose existentes para Quadlet automaticamente, reduzindo o esforço de migração.
Leia Também
- Keycloak: Autenticação Grátis com Container e C#
- Configuração .NET 8+: IOptions, Secrets e Docker
- .NET Worker e Background Service: Alto Volume
- Makefile: Automatizando tarefas para Python, Hugo e Docker
- Terminal vs Pipeline: diferenças ao executar scripts
Referências
- Podman Documentation — Rootless Containers — referência oficial para configuração e limitações do modo rootless.
- Quadlet Manual (
podman-systemd.unit) — especificação completa dos arquivos.container,.network,.volume,.kubee.pod. - CIS Docker Benchmark v1.6.0 — controles de hardening para Docker e equivalentes em Podman.
- NIST SP 800-190 — Application Container Security Guide — guia oficial dos EUA sobre segurança de containers, com foco em user namespaces.
- Red Hat — Podman vs Docker: A Detailed Comparison — comparativo técnico aprofundado mantido pela equipe que desenvolve Podman.
- Containers/Podlet GitHub — ferramenta oficial para converter
docker-compose.ymlem Quadlet. - pasta(1) — passt/pasta networking — backend de rede usado por Podman 5.x para preservar IP de origem em modo rootless. 📬
👉 Artigo completo com todos os exemplos de código: Podman Rootless em Produção: Substituindo Docker
United States
NORTH AMERICA
Related News
How Braze’s CTO is rethinking engineering for the agentic area
10h ago
Amazon Employees Are 'Tokenmaxxing' Due To Pressure To Use AI Tools
21h ago

Implementing Multicloud Data Sharding with Hexagonal Storage Adapters
15h ago

DeepMind’s CEO Says AGI May Be ~4 Years Away. The Last Three Missing Pieces Are Not What Most People Think.
15h ago

CCSnapshot - A Claude Code Configs Transfer Tool
21h ago