# Podman Quadlet 入门：把容器交给 Systemd 管理


## 概述

[Podman](https://podman.io/)（Pod Manager）是 Red Hat 开发的无守护进程容器引擎。和 Docker 最大的区别是：Podman 不需要后台守护进程，容器直接作为子进程运行，天然支持 rootless 模式，且兼容 Docker CLI 的大部分命令（`alias docker=podman` 即可无缝切换）。

Quadlet 是 Podman 内置的 **systemd generator**。它会在系统启动和执行 `systemctl daemon-reload` 时，读取特定目录下的 Quadlet 文件（`.container`、`.pod` 等），自动生成对应的 `.service` 单元文件供 Systemd 管理。你只需要写一个简单的 `.container` 文件，Quadlet 就会帮你生成完整的 Systemd 服务定义，不必手动编写复杂的 `.service` 文件。

这样做的好处：

- **自动启停**：容器作为 Systemd 服务管理，随系统或用户会话启动/停止
- **依赖管理**：用 Systemd 原生的 `Requires=`、`After=` 定义启动顺序
- **日志集成**：容器日志直接走 journald，和系统日志统一
- **资源管控**：利用 Systemd 的 cgroups 能力限制容器资源

官方文档：[podman-systemd.unit(5)](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)。本文主要介绍 rootless 容器。

## Podman(Rootless) 相对 Docker(Rootful) 的一些实际差异

常被提到的几点是：

- 没有常驻 daemon，日常管理路径更直接。
- 容器以普通用户运行时，权限边界通常比 rootful Docker 更收敛；容器出问题，影响范围一般也更小。
- 对宿主机网络栈往往更少侵入，通常不会直接接管系统级的 bridge、NAT 和防火墙规则；宿主机网络本来就比较复杂时，这点有时会省事一些。
- 如果容器里基本就是单个 UID 的进程，rootless 的 UID 映射有时能把它写出的文件对齐到执行容器的那个用户，bind mount 的属主问题会少一点。多用户或多进程镜像里，这个帮助通常就没那么明显了。
- 配合 Quadlet 使用时，可以直接交给 Systemd 管理，不用自己再拼一层容器自启动方案。
- 镜像自动更新也可以直接用 Podman 自带的机制来做，不一定需要再额外套一个更新容器。

这几条更适合当作取舍差异，不太适合当成绝对优势。具体是否更省心，还是得看镜像、挂载方式和宿主机网络环境。

## 文件类型与存放位置

Quadlet 支持的单元文件类型：

| 后缀         | 用途                                 |
| ------------ | ------------------------------------ |
| `.container` | 定义单个容器                         |
| `.pod`       | 定义一组共享网络的容器               |
| `.volume`    | 定义 Podman 卷                       |
| `.network`   | 定义 Podman 网络                     |
| `.image`     | 定义容器镜像（拉取策略等）           |
| `.build`     | 从 Containerfile/Dockerfile 构建镜像 |
| `.kube`      | 导入 Kubernetes YAML                 |
| `.artifact`  | 定义 Podman 制品（artifacts）        |

最常用的是 `.container` 和 `.pod`。

Quadlet 按优先级搜索文件，rootful 和 rootless 模式的路径不同：

**rootful（root 用户）模式：**

| 优先级 | 路径                                           |
| ------ | ---------------------------------------------- |
| 最高   | `/run/containers/systemd/`（临时，用于测试）   |
| 中     | `/etc/containers/systemd/`（管理员配置）       |
| 最低   | `/usr/share/containers/systemd/`（发行版提供） |

**rootless（普通用户）模式：**

| 优先级 | 路径                                          |
| ------ | --------------------------------------------- |
| 最高   | `$XDG_RUNTIME_DIR/containers/systemd/`        |
|        | `~/.config/containers/systemd/`（最常用）     |
|        | `/etc/containers/systemd/users/${UID}/`       |
|        | `/etc/containers/systemd/users/`              |
|        | `/usr/share/containers/systemd/users/${UID}/` |
| 最低   | `/usr/share/containers/systemd/users/`        |

Quadlet 也支持符号链接以及 systemd 风格的 drop-in 覆盖（例如 `foo.container.d/10-override.conf`）。

## 单个容器

> 以下示例均基于 rootless 模式。

下面是一个最简单的 `.container` 文件示例：

```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
```

各部分含义：

- **[Unit]** - Systemd 元信息：描述、启动顺序依赖
- **[Container]** - 容器配置：镜像、端口、挂载卷、环境变量等（Quadlet 处理）
- **[Service]** - Systemd 服务行为：如自动重启策略（透传给 Systemd）
- **[Install]** - 安装信息：`WantedBy=default.target` 表示用户登录时自动启动（透传给 Systemd）

除了 `[Container]` 等 Quadlet 自定义段，你可以在文件中使用任意标准的 Systemd 配置选项。

文件写好之后，后续操作就都是标准的 Systemd 命令，记得加 `--user`：

```shell
# 加载配置
systemctl --user daemon-reload

# 启动
systemctl --user start nginx

# 查看状态
systemctl --user status nginx

# 日志
journalctl --user -u nginx -f

# 停止
systemctl --user stop nginx
```

## 多容器编排

如果一个应用由多个容器组成，用 `.pod` 文件把它们放进同一个 Pod。Pod 内的容器共享网络，通过 `127.0.0.1` 互相访问。

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

[Install]
WantedBy=default.target
```

容器通过 `Pod=` 加入。Pod 内容器共享网络，WordPress 通过 `127.0.0.1:3306` 直连 MySQL：

```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
```

启动时只需要操作 Pod：

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

Systemd 会自动启动 Pod 内的所有容器，并根据依赖关系决定启动顺序。

## 从 Compose 迁移

如果你已经有 `docker-compose.yml`，可以用 [`podlet`](https://github.com/containers/podlet) 自动转换当前目录中的 Compose 文件：`podlet -i -a compose --pod`。更多用法可以通过 `podlet --help` 查看。

## 常见问题

### I. 保持容器在用户登出后继续运行

默认情况下，用户登出后 Systemd 的 user session 会结束，容器也会停止。需要为当前用户启用 linger，才能让容器在登出后继续运行：

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

只做一次。

### II. 兼容 docker.io 镜像拉取

Podman 默认不会从 `docker.io` 自动拉取镜像。如果你需要兼容 Docker 的镜像写法（如 `nginx:alpine`），在 `/etc/containers/registries.conf` 中添加：

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

此后 Podman 遇到不带 registry 前缀的镜像名时会自动去 docker.io 查找。如果始终使用 `docker.io/` 完整前缀，则无需此配置。

### III. Pasta 网络下无法通过公网 IP 访问宿主机

Podman 5 之后默认使用 Pasta 网络后端，容器会共享宿主机的公网 IP。这时可以通过配置独立的内部网络来解决：

```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]
# 允许容器内通过 host.containers.internal 访问宿主机
host_containers_internal_ip = "10.0.2.2"
```

### IV. Quadlet 文件放在正确路径后，仍然没有生成对应的 Systemd 单元文件

更新 Quadlet 文件后，需要执行 `systemctl --user daemon-reload` 让 Systemd 重新生成对应的 unit。如果仍然没有看到生成的单元文件，很可能是 Quadlet 配置本身有错误。可以运行 `/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun` 查看输出，再结合 `grep` 过滤对应文件名，定位具体报错信息。

### V. 镜像自动更新

在需要自动更新的 `[Container]` 部分添加 `AutoUpdate=registry`，然后启用 Podman 的自动更新定时器：

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

### VI. 更方便地编写 Quadlet 配置

大多数情况下，直接使用 podlet 就可以把 `podman run` 命令和 Compose 文件转换为 Quadlet 配置，再稍作修改即可使用。不过有些场景下，Compose 文件本身很复杂，里面的容器也很多，这时可以借助 AI Agent，用 [Quadlet Creator](/posts/quadlet-creator/) 这类 skill 来处理。

## 延伸阅读

<!-- - [Quadlet 实战：部署 Immich](/posts/podman-quadlet-immich/) -->

- [Quadlet Creator：让 AI Agent 把 Docker 迁移到 Quadlet](/posts/quadlet-creator/)
- [podman-systemd.unit(5) - 官方文档](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)
- [Podman 官方网站](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/)


---

> 作者: Nite  
> URL: https://www.nite07.com/zh-cn/posts/podman/  

