If you run containers in production, Docker Compose is easy to use, but it needs a background daemon running all the time. Podman Quadlet and systemd let you run the same multi-container stack without that daemon, while systemd handles restarts, startup at boot, and logs on its own. In this guide, you will learn building a small web stack using only Quadlet unit files and systemd.
Rootless Container Deployment Using Podman Quadlet and systemd
Table of Contents
- What Is Podman Quadlet and Systemd
- Requirements Before You Start
- Enable Rootless Containers
- Multi-Container Setup Using Podman Quadlet and Systemd
- Create the Network Unit
- Create the Volume Unit
- Create an Environment File
- Create the Image Unit (Optional Pre-Pull)
- Create the Database Container Unit
- Create the App Container Unit
- Create the Reverse Proxy Container Unit
- Load and Start the Quadlet Units
- Enable Automatic Startup for Containers at Boot
- View Container Logs via journald
- Update Images and Restart Services
- Conclusion

What Is Podman Quadlet and Systemd
Quadlet is a tool built into Podman. You write small text files that describe a container, a network, a volume, or an image. Systemd reads these files and turns each one into a real .service unit automatically.
Podman runs the containers, and systemd handles restarts, dependencies, boot order, and logging.
This is different from podman-compose, which copies Docker Compose's YAML style but still needs an extra tool running to work. It's also different from the older podman generate systemd command, which made fixed unit files from a container that was already running. That old method is deprecated now.
Quadlet files are the current, supported way, and systemd updates the service automatically whenever you edit a Quadlet file and reload.
Requirements Before You Start
You need a Linux server running Ubuntu 24.04 or newer with a recent kernel and systemd. Check these two things first, since Quadlet will not work without them.
- Podman version 4.4 or newer. This guide uses the current stable Podman 6.x release.
cgroup v2enabled, which is the default on modern distributions like Ubuntu 24.04.
Run this to confirm cgroup v2 is active:
It should print v2. If you are testing on a VPS, a Linux server with a recent kernel is the easiest place to try this out cleanly. If you don't already have one, a lightweight Linux VPS from PerLod is a good option because it gives you full root access to set up rootless Podman properly without extra restrictions.
Enable Rootless Containers
Running containers as a normal user, without root and without a daemon, is the safer default for production. This is one of the biggest reasons people move to Podman Quadlet and systemd instead of Docker.
First, you must make sure your user can start systemd services even when not logged in by enabling lingering:
Without this, your rootless containers would stop the moment you log out of SSH.
Next, you must create the folder where your user-level Quadlet files live:
Podman's generator scans this exact folder for rootless units, along with $XDG_RUNTIME_DIR/containers/systemd/ for temporary units. Files placed here are read at boot and whenever you run systemctl --user daemon-reload.
Multi-Container Setup Using Podman Quadlet and Systemd
Here we will deploy a Postgres database, a small app container, and an Nginx reverse proxy in front of it. Each piece gets its own Quadlet file. This is where Podman Quadlet and systemd actually replace your docker-compose.yml.
Move into your Quadlet folder before creating files:
Create the Network Unit
Containers in the same stack need to talk to each other by name, so you must create a shared network first:
Paste this content into the file:
Save and exit. This .network file will generate a systemd service called app-network.service, which creates a Podman network named app-net when started.
Create the Volume Unit
Every time a container restarts or gets rebuilt, it normally loses whatever was inside it. That's a problem for a database, since you can't afford to lose your data every time the container stops.
A volume solves this by keeping the data stored outside the container, on the host, so it stays safe no matter what happens to the container itself.
Paste this content into the file:
Create an Environment File
Don't put passwords directly inside the unit file. You must keep them in a separate environment file instead. Create the file with:
Add your database credentials:
Now make sure only you can read this file:
Create the Image Unit (Optional Pre-Pull)
An .image unit tells Podman to download an image early, as its own systemd service, instead of waiting until the container first starts. This helps if your network is slow or the image is large. It also lets other units wait for the download to finish before they start.
Create the Database Container Unit
Now it's time to create the container that will actually run the database. This file tells Podman which image to use, which network and volume to connect, and how to check that the database is healthy.
Create the App Container Unit
With the database ready, the next step is to create the app container itself. This one runs your actual application and connects to the database over the shared network.
Replace the httpd image with your real application image in a production setup. The NetworkAlias=app line lets the reverse proxy reach this container using the hostname app instead of an IP address.
Create the Reverse Proxy Container Unit
The last piece is the reverse proxy. It sits in front of your app and forwards incoming traffic to it, so the app itself doesn't need to be exposed directly.
Note: The :ro,Z suffix mounts the file as read-only and adds an SELinux label. You only need this if you're on Fedora or RHEL, since SELinux checks it.
Then, create a matching config file so Nginx has something to load:
Load and Start the Quadlet Units
Every time you add or edit a Quadlet file, you must tell systemd to re-read your unit directory:
Now start everything in order. Because you added After= and Requires= in the [Unit] sections, systemd already knows the network and volume must come up before the containers, so you only need to start the top-level services:
Note: If you installed Podman using the static binary method instead of apt, you may hit a conmon failed: exit status 1 error with journald logging, since that build's bundled conmon lacks journald support. Fix it by installing the distro conmon package (sudo apt install conmon) so it takes priority on PATH, or switch LogDriver=journald to LogDriver=k8s-file in your .container files.
Check that everything is running:
You should see active (running) for each, and after the health-start period passes, podman ps will show a healthy status next to each container.
Enable Automatic Startup for Containers at Boot
You already added [Install] WantedBy=default.target to each .container file. Because of that, systemd applies the boot-startup setting automatically as soon as the file loads; you don't need to run systemctl enable for these services.
Combined with loginctl enable-linger from earlier, this is how Podman Quadlet and systemd start your containers at boot, without needing you to log in or run a root daemon in the background.
To confirm the boot behavior is registered, dry-run the generator:
This prints the exact .service file systemd will create from each Quadlet unit, which is the fastest way to catch a typo before it causes a real failure.
Note: If you installed Podman using the static binary method, the quadlet generator binary may not be symlinked into systemd's expected paths, causing Unit ... not found errors. Fix it with:
View Container Logs via journald
One advantage of using Podman Quadlet and systemd together is that you get unified logging for free. Since every container unit uses LogDriver=journald, you read logs exactly like any other systemd service.
You don't need an extra log container just to see basic output. But if you'd rather see everything on a simple dashboard than read logs, check out our guide on self-hosting Beszel for VPS and Docker monitoring. It shows you an easy way to see container health across your server.
Update Images and Restart Services
To pull a new image and redeploy a service, update the Image= value in the relevant .container file or just re-pull the same tag, then run:
For automatic updates on a schedule, Podman also supports AutoUpdate=registry inside the [Container] section, paired with the built-in podman-auto-update.timer.
Conclusion
At this point, you have a real multi-container stack running with Podman Quadlet and systemd. Rootless, with health checks, dependencies, journald logging, and boot startup, all without Docker Compose or a background daemon. It's easier to manage because it integrates seamlessly with the systemctl and journalctl tools you already use.
We hope you enjoy this guide. For more detailed information, you can check the Podman Quadlet official documentation.
No. Quadlet is a Podman-only feature and does not need Docker or Docker Compose at all.
In ~/.config/containers/systemd/ for your user, or /etc/containers/systemd/ for root-level units.
Yes, using a .build unit, which builds an image from a Containerfile as its own systemd service.