# Podman Quadlet Basics: Let Systemd Manage Your Containers


## Overview

[Podman](https://podman.io/) (Pod Manager) is a daemonless container engine developed by Red Hat. Compared with Docker, the biggest difference is that Podman does not require a background daemon. Containers run directly as child processes, rootless mode is supported natively, and most Docker CLI commands are compatible with it (`alias docker=podman` is often enough for a smooth switch).

Quadlet is Podman's built-in **systemd generator**. At boot time and whenever you run `systemctl daemon-reload`, it reads Quadlet files from specific directories (such as `.container` and `.pod`) and automatically generates the corresponding `.service` unit files for Systemd to manage. In other words, you write a simple `.container` file, and Quadlet turns it into a complete Systemd service, so you do not need to hand-write a complex `.service` file yourself.

Benefits of this approach:

- **Automatic start and stop**: containers are managed as Systemd services and can follow system boot or user session startup/shutdown
- **Dependency management**: use native Systemd directives such as `Requires=` and `After=` to define startup order
- **Log integration**: container logs go directly into journald, unified with system logs
- **Resource control**: use Systemd cgroups to limit container resources

Official documentation: [podman-systemd.unit(5)](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html). This article mainly covers rootless containers.

## Some Practical Differences Between Podman (Rootless) and Docker (Rootful)

A few commonly mentioned ones are:

- There is no long-running daemon, so the day-to-day management path is more direct.
- When containers run as a regular user, the privilege boundary is usually tighter than with rootful Docker. If something goes wrong inside the container, the blast radius is often smaller too.
- It tends to be less invasive to the host network stack. It usually does not directly take over system-level bridge, NAT, and firewall rules, so it can be less troublesome on hosts with more complex networking.
- If a container mostly runs a single-UID process, rootless UID mapping can sometimes align written files with the user who launched the container, which helps reduce bind mount ownership confusion. In multi-user or multi-process images, this usually helps much less.
- When used with Quadlet, you can hand container lifecycle management directly to Systemd instead of building another layer just for auto-start.
- Image auto-updates can also be handled with Podman's built-in mechanism, without necessarily adding a separate updater container.

These points are better treated as trade-offs than absolute advantages. Whether it is actually more convenient still depends on the image, the bind mounts, and the host networking setup.

## File Types and Locations

Quadlet supports these unit file types:

| Suffix       | Purpose                                 |
| ------------ | --------------------------------------- |
| `.container` | Define a single container               |
| `.pod`       | Define a group of containers sharing networking |
| `.volume`    | Define a Podman volume                  |
| `.network`   | Define a Podman network                 |
| `.image`     | Define a container image (pull policy, etc.) |
| `.build`     | Build an image from a Containerfile/Dockerfile |
| `.kube`      | Import Kubernetes YAML                  |
| `.artifact`  | Define Podman artifacts                 |

The most commonly used ones are `.container` and `.pod`.

Quadlet searches for files by priority, and the paths differ between rootful and rootless mode:

**Rootful mode (root user):**

| Priority | Path                                           |
| -------- | ---------------------------------------------- |
| Highest  | `/run/containers/systemd/` (temporary, useful for testing) |
| Medium   | `/etc/containers/systemd/` (administrator configuration) |
| Lowest   | `/usr/share/containers/systemd/` (provided by the distribution) |

**Rootless mode (regular user):**

| Priority | Path                                          |
| -------- | --------------------------------------------- |
| Highest  | `$XDG_RUNTIME_DIR/containers/systemd/`        |
|          | `~/.config/containers/systemd/` (most common) |
|          | `/etc/containers/systemd/users/${UID}/`       |
|          | `/etc/containers/systemd/users/`              |
|          | `/usr/share/containers/systemd/users/${UID}/` |
| Lowest   | `/usr/share/containers/systemd/users/`        |

Quadlet also supports symbolic links and systemd-style drop-in overrides (for example, `foo.container.d/10-override.conf`).

## Single Container

> All examples below assume rootless mode.

Here is a minimal `.container` file:

```ini
# nginx.container
[Unit]
Description=Nginx container
After=network-online.target

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

[Service]
Restart=always

[Install]
WantedBy=default.target
```

What each section means:

- **[Unit]**: Systemd metadata such as description and startup ordering dependencies
- **[Container]**: container configuration such as image, ports, volumes, and environment variables (processed by Quadlet)
- **[Service]**: Systemd service behavior, such as restart policy (passed through to Systemd)
- **[Install]**: installation metadata. `WantedBy=default.target` means auto-start when the user logs in (passed through to Systemd)

Besides Quadlet-specific sections like `[Container]`, you can use any standard Systemd configuration options in the file.

Once the file is written, everything else is standard Systemd. In rootless mode, remember to add `--user`:

```shell
# Reload configuration
systemctl --user daemon-reload

# Start
systemctl --user start nginx

# Check status
systemctl --user status nginx

# Logs
journalctl --user -u nginx -f

# Stop
systemctl --user stop nginx
```

## Multi-Container Orchestration

If an application consists of multiple containers, use a `.pod` file to place them into the same Pod. Containers inside a Pod share networking and can talk to each other via `127.0.0.1`.

```ini
# wordpress.pod
[Pod]
PublishPort=8080:80

[Install]
WantedBy=default.target
```

Containers join the Pod via `Pod=`. Since containers inside the Pod share networking, WordPress can connect directly to MySQL using `127.0.0.1:3306`:

```ini
# db.container
[Container]
Image=docker.io/library/mysql:8.0
Pod=wordpress.pod
Environment=MYSQL_ROOT_PASSWORD=***
Environment=MYSQL_DATABASE=wordpress
Environment=MYSQL_USER=wpuser
Environment=MYSQL_PASSWORD=***
Volume=/path/to/data:/var/lib/mysql

[Service]
Restart=always
```

```ini
# wp.container
[Container]
Image=docker.io/library/wordpress:php8.2-apache
Pod=wordpress.pod
Environment=WORDPRESS_DB_HOST=127.0.0.1:3306
Environment=WORDPRESS_DB_USER=wpuser
Environment=WORDPRESS_DB_PASSWORD=***
Environment=WORDPRESS_DB_NAME=wordpress
Volume=wp-content:/var/www/html/wp-content

[Service]
Restart=always
```

To start everything, you only need to operate on the Pod:

```shell
systemctl --user start wordpress-pod
```

Systemd will automatically start all containers in the Pod and determine the startup order based on dependencies.

## Migrating from Compose

If you already have a `docker-compose.yml`, you can use [`podlet`](https://github.com/containers/podlet) to automatically convert the Compose file in the current directory: `podlet -i -a compose --pod`. For more usage details, check `podlet --help`.

## Common Issues

### I. Keeping Containers Running After Logout

By default, containers stop when the user logs out. To keep them running, enable linger for the current user:

```shell
sudo loginctl enable-linger $USER
```

You only need to do this once.

### II. Making Image Pulls Compatible with docker.io

Podman does not automatically pull images from `docker.io` by default. If you want Docker-style image names like `nginx:alpine` to work, add this to `/etc/containers/registries.conf`:

```toml
unqualified-search-registries = ["docker.io"]
```

After that, Podman will automatically search `docker.io` when it sees an image name without a registry prefix. If you always use the full `docker.io/` prefix, this configuration is unnecessary.

### III. Unable to Reach the Host via Public IP with Pasta Networking

Starting with Podman 5, Pasta is the default networking backend. Containers share the host's public IP in this mode. If you need a separate internal network instead, you can configure one like this:

```toml
# ~/.config/containers/containers.conf
[network]
pasta_options = ["-a", "10.0.2.0", "-n", "24", "-g", "10.0.2.2", "--dns-forward", "10.0.2.3", "--map-gw"]

[containers]
# Allow containers to reach the host via host.containers.internal
host_containers_internal_ip = "10.0.2.2"
```

### IV. The Quadlet File Is in the Right Place but No Systemd Unit Is Generated

After updating a Quadlet file, run `systemctl --user daemon-reload` to regenerate the corresponding unit. If the Systemd unit still does not appear, the Quadlet configuration may contain an error. You can inspect the specific error with `/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun | grep <quadlet-file-name>`.

### V. Automatic Image Updates

Add `AutoUpdate=registry` to the `[Container]` section that should be updated automatically, then enable Podman's auto-update timer:

```shell
systemctl --user enable --now podman-auto-update.timer
```

### VI. Writing Quadlet Configs More Conveniently

In most cases, `podlet` can easily convert `podman run` commands and Compose files into Quadlet configs, and you only need to make small adjustments afterward. But when a Compose setup becomes very complex and includes many containers, an AI agent with a skill like [Quadlet Creator](/posts/quadlet-creator/) can help.

## Further Reading

<!-- - [Quadlet in Practice: Deploying Immich](/posts/podman-quadlet-immich/) -->

- [Quadlet Creator: Let AI Agents Migrate Docker to Quadlet](/posts/quadlet-creator/)
- [podman-systemd.unit(5) - Official Documentation](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)
- [Podman Official Website](https://podman.io/)
- [Using Quadlet with Podman - Red Hat Blog](https://www.redhat.com/sysadmin/quadlet-podman)
- [Podman v5.0 Breaking Changes in Detail](https://blog.podman.io/2024/03/podman-5-0-breaking-changes-in-detail/)
- [Podman 5.3 Changes for Improved Networking Experience with Pasta](https://blog.podman.io/2024/10/podman-5-3-changes-for-improved-networking-experience-with-pasta/)


---

> Author: Nite  
> URL: https://www.nite07.com/en/posts/podman/  

