Quadlets

TL;DR: Docker is out, Podman is in, and I’m running everything as a regular user through systemd quadlets. It took longer than it should have and I’d do it again.

SectionSummary
Why I left DockerThe daemon, the root, and the slow drift to Podman
What a quadlet actually isA .container file systemd turns into a service
Why rootless mattersWhere the attack surface goes when nothing runs as root
The migration was not painlessDocker-socket tools and other varmints

Why I left Docker

Over the years I’ve been methodically replacing my Docker setup with Podman and migrating my containers to run at the user level through systemd quadlets. No daemon running as root, native systemd integration, and containers that behave like proper services instead of processes you’re just hoping stay alive.

Docker’s architecture has one feature I’d grown ill with: a single daemon, running as root, that every container lives or dies by. It’s a fat process holding all the privilege and all the state, and if it falls over it takes everything with it. Podman has no daemon. Each container is a plain child process, and the thing supervising it is the init system the machine already runs — systemd. No second supervisor reinventing what systemd already does well.

What a quadlet actually is

A quadlet is a .container file — a systemd unit, written in the same INI-ish format as any other unit, that Podman’s generator reads at boot and turns into a real .service. You write something declarative; systemd does the rest. Here’s a stripped-down one:

[Unit]
Description=Example service
After=network-online.target

[Container]
Image=docker.io/library/caddy:latest
PublishPort=8080:80
Volume=%h/containers/caddy/data:/data:Z
AutoUpdate=registry

[Service]
Restart=always

[Install]
WantedBy=default.target

Drop that in ~/.config/containers/systemd/, run systemctl --user daemon-reload, and you’ve got a service. systemctl --user start example brings it up. Logs go to the journal like anything else. It restarts on failure, comes up on login, and AutoUpdate=registry lets podman auto-update pull new images on a timer. The whole container is described by one text file that lives in version control, not by a shell history of docker run flags I’ve long since forgotten.

Why rootless matters

Running as a regular user instead of root is the part that earns its keep. With rootless Podman, the container’s “root” is mapped — through user namespaces — to an unprivileged range of subordinate UIDs on the host. Root inside the container is nobody in particular outside it. A container breakout, the nightmare scenario, lands the attacker as an unprivileged user with no path to the rest of the box, instead of as host root.

That’s the theory, anyway, and I’m about 85% sold on it. You’re not deleting the attack surface so much as moving it — the kernel’s user-namespace code is now doing load-bearing work, and that code has had its own bad days. But “a breakout gets you a powerless user” beats “a breakout gets you the whole machine” by a country mile. I’ll take the trade.

The migration was not painless

Podman is not a drop-in replacement in every meaningful sense, and some tools have the Docker socket burned so deep into their DNA that migrating them felt like performing surgery with oven mitts. Anything that expects to talk to /var/run/docker.sock and issue daemon commands has to be coaxed onto Podman’s socket, and a few of them bow up about it. I got there eventually, with enough workarounds to wallpaper a small room.

Right now I’m focused on three things:

The highlight so far: I packed an old Half-Life 1 mod into an Alpine image and got it connecting to the Steam network. Completely unnecessary. Worth every minute.


Open Questions

Open questions I’m still chewing on. Is rootless Podman actually more secure in practice, or have I just moved the attack surface somewhere I can’t see it as easily? For the DNS replacement — is there anything that won’t also wake me up at 2am, or is that just the nature of DNS? And the tools with the Docker socket burned into their DNA: at what point is it cheaper to replace the tool than to keep wallpapering over it?