Podman Quadlet 入门:把容器交给 Systemd 管理
概述
Podman(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)。本文主要介绍 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 文件示例:
# 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:
# 加载配置
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 互相访问。
# wordpress.pod
[Pod]
PublishPort=8080:80
[Install]
WantedBy=default.target容器通过 Pod= 加入。Pod 内容器共享网络,WordPress 通过 127.0.0.1:3306 直连 MySQL:
# 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# 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:
systemctl --user start wordpress-podSystemd 会自动启动 Pod 内的所有容器,并根据依赖关系决定启动顺序。
从 Compose 迁移
如果你已经有 docker-compose.yml,可以用 podlet 自动转换当前目录中的 Compose 文件:podlet -i -a compose --pod。更多用法可以通过 podlet --help 查看。
常见问题
I. 保持容器在用户登出后继续运行
默认情况下,用户登出后 Systemd 的 user session 会结束,容器也会停止。需要为当前用户启用 linger,才能让容器在登出后继续运行:
sudo loginctl enable-linger $USER只做一次。
II. 兼容 docker.io 镜像拉取
Podman 默认不会从 docker.io 自动拉取镜像。如果你需要兼容 Docker 的镜像写法(如 nginx:alpine),在 /etc/containers/registries.conf 中添加:
unqualified-search-registries = ["docker.io"]此后 Podman 遇到不带 registry 前缀的镜像名时会自动去 docker.io 查找。如果始终使用 docker.io/ 完整前缀,则无需此配置。
III. Pasta 网络下无法通过公网 IP 访问宿主机
Podman 5 之后默认使用 Pasta 网络后端,容器会共享宿主机的公网 IP。这时可以通过配置独立的内部网络来解决:
# ~/.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 的自动更新定时器:
systemctl --user enable --now podman-auto-update.timerVI. 更方便地编写 Quadlet 配置
大多数情况下,直接使用 podlet 就可以把 podman run 命令和 Compose 文件转换为 Quadlet 配置,再稍作修改即可使用。不过有些场景下,Compose 文件本身很复杂,里面的容器也很多,这时可以借助 AI Agent + Skills 来处理。