Skip to content
SumGuy's Ramblings
Go back

Podman Quadlets: Running Containers Without the Docker Daemon (or Your Sanity)

You Don’t Actually Need the Docker Daemon

Let me paint you a picture. It’s 2 AM. Your home server is running Docker, which is running a daemon, which is running as root, which is silently doing whatever it wants to your filesystem. Your Nginx container restarted itself three times for reasons unknown, your Watchtower container is updating things you didn’t ask it to update, and somewhere in that mess is a compose file you last touched eight months ago and no longer fully trust.

You know what would’ve helped? Not having a single all-knowing daemon sitting between you and your containers like an overconfident middle manager.

That’s the pitch for Podman. And once you add Quadlets into the mix — containers that systemd manages natively, like any other service — you get something that actually feels like it was designed for the way sane people run servers.

Let’s dig in.


What Even Is Podman?

Podman (short for Pod Manager) is a container engine that does what Docker does — pulls images, runs containers, manages volumes and networks — but without the daemon.

No background service. No root requirement. No single point of failure that takes down every container on your machine if it crashes.

Here’s the thing most people don’t realize: Podman is almost entirely CLI-compatible with Docker. Like, embarrassingly compatible. This works:

alias docker=podman

That’s it. Most of your existing Docker commands just work. Same flags, same image format (OCI-compatible), same registries. It’s not a gotcha, it’s literally by design.

Installing Podman on Ubuntu/Debian

sudo apt update
sudo apt install -y podman
podman --version

On Ubuntu 22.04+, you’ll get Podman 3.x from the default repos. For Podman 4.4+ (which you need for Quadlets), add the Kubic repo:

# For Ubuntu 22.04
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_22.04/ /" \
  | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list

curl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_22.04/Release.key \
  | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/devel_kubic_libcontainers_unstable.gpg

sudo apt update && sudo apt install -y podman

On Fedora or RHEL-based distros, Podman is already installed by default and you’re already living in the future.

The Rootless Part Actually Matters

Running containers as root is like giving every delivery driver a master key to your house because it’s easier than handing out specific room keys. Technically functional. Deeply cursed.

Podman’s rootless mode means containers run under your own user account. If something goes wrong inside a container — escape attempt, misconfiguration, whatever — it’s limited to what your user can do. No root access means no “oops, container wrote to /etc” incidents.

# This runs as YOU, not root
podman run --rm hello-world

# Compare: Docker (traditionally requires sudo or docker group — which is basically root anyway)
docker run --rm hello-world

Being in the docker group is functionally equivalent to sudo access, by the way. That’s not a hot take, it’s documented by Docker themselves. Podman sidesteps this entirely.


Enter Quadlets: Systemd Actually Managing Your Containers

Okay, so you’ve switched to Podman. Now what? How do you make containers start on boot, restart on failure, and generally behave like proper services?

The old answer was podman generate systemd — a command that would spit out a systemd unit file based on an existing container. It worked, but it was janky. The generated files were verbose, hard to maintain, and felt like they were auto-generated (because they were). Every time you changed your container config, you had to regenerate the unit file.

The new answer is Quadlets.

Quadlets, introduced in Podman 4.4, let you write simple .container files that systemd reads natively through a generator. Instead of a 60-line auto-generated unit file, you write a clean INI-style config that looks like this:

[Unit]
Description=My Nginx Container

[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80

[Service]
Restart=always

[Install]
WantedBy=default.target

Drop that in the right directory, run two commands, and systemd is managing your container. That’s it.

Where Do Quadlet Files Live?

For rootless (user) containers:

~/.config/containers/systemd/

For system-wide (root) containers:

/etc/containers/systemd/

Your First Quadlet: Nginx

Let’s do this properly. Create the file:

mkdir -p ~/.config/containers/systemd/
nano ~/.config/containers/systemd/nginx.container
[Unit]
Description=Nginx Web Server
After=network-online.target

[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80
Volume=%h/nginx/html:/usr/share/nginx/html:ro,Z

[Service]
Restart=on-failure
TimeoutStartSec=30

[Install]
WantedBy=default.target

A few notes:

Now tell systemd to pick it up and start it:

# Reload the systemd user daemon to pick up new units
systemctl --user daemon-reload

# Enable and start
systemctl --user enable --now nginx.service

# Check status
systemctl --user status nginx.service

# Watch logs
journalctl --user -u nginx.service -f

That’s a container running as a proper systemd service. No Docker Compose, no daemon, no root. Just systemctl.


A More Complete Example: Web App with Volumes and Env Vars

Real workloads need environment variables and persistent storage. Here’s a more complete example — let’s say you’re running a simple web app:

[Unit]
Description=My Awesome Web App
After=network-online.target postgresql.service

[Container]
Image=docker.io/myuser/myapp:latest
PublishPort=3000:3000

# Environment variables
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=%h/.config/containers/myapp.env

# Persistent storage
Volume=myapp-data:/app/data:Z
Volume=%h/myapp/config:/app/config:ro,Z

# Resource limits (because containers without limits are just vibes)
PodmanArgs=--memory=512m --cpus=0.5

[Service]
Restart=on-failure
RestartSec=10s
TimeoutStartSec=60

[Install]
WantedBy=default.target

Your myapp.env file:

DATABASE_URL=postgresql://user:password@localhost:5432/mydb
SECRET_KEY=your-secret-here

Notice EnvironmentFile — same pattern as a regular systemd service. Your secrets stay out of the unit file, which is a good habit regardless of whether you’re using containers.


Networks as Quadlets Too

If you’re running multiple containers that need to talk to each other, you’ll want a dedicated network. That’s also a Quadlet:

# ~/.config/containers/systemd/myapp.network
[Network]
Driver=bridge

Then reference it in your container files:

[Container]
Image=docker.io/myuser/myapp:latest
Network=myapp.network

Podman will create the network as a systemd service (myapp-network.service) and your container unit will automatically depend on it. The dependency graph is managed for you.


How This Compares to Docker Compose

Let’s be real for a second. Docker Compose is genuinely good for local dev and single-host deployments. It’s readable, it handles dependencies, and pretty much every self-hosted project ships a docker-compose.yml. Abandoning it entirely is not necessarily the right call.

Here’s an honest comparison:

Docker ComposePodman + Quadlets
Startup on bootNeeds docker-compose up in cron or rc.localNative systemd, just WantedBy=default.target
Restart on failureCompose restart: always (polling-based)systemd supervision (proper)
Logsdocker compose logsjournalctl (integrated with system logs)
Root requiredUsually yes (or docker group)No, fully rootless
Multi-container orchestrationSingle compose fileMultiple unit files + network quadlets
Learning curveLow if you know YAMLSlightly higher (systemd concepts)
Daemon requiredYesNo

Compose wins on simplicity when you’re just getting something running quickly. Quadlets win on long-term maintainability, proper service management, and security posture. They’re not mutually exclusive either — podman-compose exists as a drop-in for those transitioning.


When to Use Which

Stick with Docker + Compose if:

Switch to Podman + Quadlets if:

Honestly, the “I already know Docker” angle is mostly a non-issue. The CLI is identical for 90% of operations. The real mental shift is from “containers managed by Docker Compose” to “containers managed by systemd.” If you already think in terms of services and units, Quadlets are going to feel immediately natural.


A Note on the podman generate systemd Ghost

You’ll still find a lot of tutorials recommending podman generate systemd --new --name mycontainer. This was the pre-Quadlet approach and it worked — it generated a full systemd unit file from a running container. But it produced ugly, hard-to-maintain output, and the workflow was backwards: run the container first, then generate the service definition from it.

Quadlets flip this around. You define the intent, systemd handles the execution. It’s the right direction.

If you’re on Podman < 4.4 and can’t upgrade, generate systemd is still there. But if you’re setting up anything new, just use Quadlets.


The Practical Upshot

Here’s what the full loop looks like once you’ve got a Quadlet set up:

# Deploy a new version (update the image tag in your .container file, then:)
systemctl --user restart myapp.service

# Check what's running
podman ps

# Roll back (change image tag back, restart)
systemctl --user restart myapp.service

# See why it crashed
journalctl --user -u myapp.service --since "10 minutes ago"

# Stop it
systemctl --user stop myapp.service

# Remove the service entirely
systemctl --user disable myapp.service
# Delete the .container file
systemctl --user daemon-reload

That’s a container lifecycle managed entirely through standard Linux tooling. No separate CLI to remember, no daemon to babysit, no compose file that’s technically a different tool entirely.


Go Forth and Run Rootless

Podman and Quadlets aren’t a revolution — they’re more like a quiet correction. Container management got complicated in ways it didn’t need to be, and this stack politely unfastens some of that complexity. You get containers that behave like services, run without root, and integrate with the init system that’s already managing everything else on your server.

Is it worth migrating your entire Docker setup tomorrow? Probably not. But for new services, for anything where security actually matters, or for anyone who’s ever thought “I wish this container just acted like a normal systemd service” — Podman + Quadlets is genuinely the answer.

Start with one container. Write the .container file. Run systemctl --user enable --now. Watch it just work.

Your 2 AM self will appreciate it.


Share this post on:

Previous Post
Tailscale Deep Dive: Mesh VPN That Just Works (and Why That's Suspicious)
Next Post
Vaultwarden Organization Sharing: Password Management for Your Whole Household (or Team)