Podman Rootless + Quadlet 部署 PostgreSQL 实践总结
AI 本文由人机协作完成,所有代码示例均已通过人工审核与验证。
本文以 PostgreSQL 为例,说明在 Debian 系 Linux 上使用 Podman Rootless 模式、通过 Quadlet 实现开机自启、并保证数据卷权限稳定的完整实践。
1. 总体原则
Podman 是 daemonless 的,Debian 安装后不会自动创建默认运行用户。
推荐为 Podman 创建专用普通用户 podman,以 Rootless 模式运行容器。
开机自启通过 systemd + Quadlet 实现,不使用 systemctl enable。
PostgreSQL 属于有状态服务,必须使用固定 UIDMap / GIDMap,避免 UserNS=auto 动态分配导致数据卷权限漂移。
keep-id 存在安全隐患,不推荐用于数据库和网络服务。
2. 安装 Podman
# apt update
# apt install -y podman uidmap slirp4netns fuse-overlayfsNote | 此处及下文代码块中的指令,以 # 开头的需 root 权限执行(普通用户需要添加 sudo),以 $ 开头的则由 podman 用户执行。 |
3. 创建非 root 用户
3.1. 创建 podman 用户
# useradd -m -s /bin/bash podman3.2. 分配从属 UID/GID 范围
# usermod --add-subuids 100000-165535 --add-subgids 100000-165535 podman验证:
# grep podman /etc/subuid /etc/subgid
/etc/subuid:podman:100000:65536
/etc/subgid:podman:100000:655364. 启用 Linger
Rootless 用户的 systemd 用户实例默认在用户登录后才启动。必须启用 linger,容器才能在系统启动时无人值守运行。
# loginctl enable-linger podman验证:
# loginctl show-user podman --property=Linger
Linger=yes5. 切换到 podman 用户
5.1. 推荐方式:run0
从 root 切换到 podman 用户,推荐使用 run0:
# run0 -u podmanrun0 会建立完整的用户会话环境,正确设置 XDG_RUNTIME_DIR 和 D-Bus 相关变量。
5.2. 裸 su podman 的问题
不要使用裸 su podman。裸 su 会保留原 root 用户的大部分环境变量,其中 XDG_RUNTIME_DIR 仍指向 /run/user/0。此时以 podman 用户运行 Podman 会出现:
Failed to obtain podman configuration: mkdir /run/user/0/libpod: permission denied或:
Failed to connect to user scope bus via local transport:
$DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined因此应始终使用 run0 -u podman 进入 podman 用户环境。
6. Quadlet 配置文件
6.1. 文件位置与命名
Rootless Quadlet 文件放在:
~/.config/containers/systemd/文件名决定服务名:
postgres-18-alpine.container -> postgres-18-alpine.service6.2. PostgreSQL Quadlet 示例
在 podman 用户 shell 中创建文件:
$ mkdir -p ~/.config/containers/systemd文件:~/.config/containers/systemd/postgres-18-alpine.container
[Unit]
Description=PostgreSQL 18 Alpine (Rootless)
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/library/postgres:18-alpine
ContainerName=postgres-18-alpine
PublishPort=5432:5432
Environment=POSTGRES_PASSWORD=change_me
Environment=TZ=Asia/Shanghai
# PostgreSQL 18+ 推荐将卷挂载到 /var/lib/postgresql
Volume=postgres-data:/var/lib/postgresql:Z
# 固定 UID/GID 映射:容器内 0-4095 -> 中间 UID 100000-104095
# UIDMap/GIDMap 与 UserNS= 互斥,不能同时写
UIDMap=0:100000:4096
GIDMap=0:100000:4096
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target说明:Debian 默认通常不启用 SELinux,卷挂载后的 :Z 可省略;若在 SELinux 系统上运行,应保留。
6.3. 卷文件实际保存地址
Rootless 模式下,命名卷 postgres-data 实际保存在 podman 用户的家目录下:
/home/podman/.local/share/containers/storage/volumes/postgres-data/_data容器内挂载点为 /var/lib/postgresql。PostgreSQL 18+ 镜像会将实际数据放在该卷内的版本化子目录中,例如:
/home/podman/.local/share/containers/storage/volumes/postgres-data/_data/18/docker可通过以下命令确认实际挂载点:
$ podman volume inspect postgres-data --format '{{.Mountpoint}}'7. 权限与 UID 映射
如果容器被删除后再创建,UID 可能被重新分配,导致新容器的用户无权限访问原先容器创建的卷文件,因此我们通过固定 UIDMap 确保容器因升级等原因重新创建后仍然有正确的权限。
7.1. 固定 UIDMap / GIDMap
UIDMap=0:100000:4096
GIDMap=0:100000:4096三个数字含义为:
容器UID:宿主机(中间)UID:映射数量上述配置表示:容器内 UID 0-4095 映射到宿主机中间 UID 100000-104095。
PostgreSQL Alpine 镜像中 postgres 用户通常为 UID/GID 70,Debian 镜像通常为 999,均落在 0-4095 范围内,因此 4096 个 ID 对 PostgreSQL 足够。
前文为用户 podman 分配了 100000-165535 共 65536 个可用的 UID,如果按每个容器分配 4096 可以用于 16 个容器,能满足大多数个人用户的需求。
7.2. 验证映射
在 podman 用户 shell 中执行:
$ podman exec postgres-18-alpine cat /proc/self/uid_map7.3. 为什么数据库不用 UserNS=auto 或 keep-id
UserNS=auto 会从从属 ID 池中动态分配子范围。容器删除重建、修改配置后重建等场景可能分配到不同子范围,导致数据卷中旧文件所有者 UID 与新容器映射 UID 不匹配,出现 Permission denied。
keep-id 可能将宿主机 root 意外映射进容器,覆盖镜像 USER 指令,并独占全部从属 ID,不适合数据库和网络服务。
固定 UIDMap / GIDMap 可保证每次重建容器时映射不变,数据卷权限长期稳定。
8. 启动与验证
在 podman 用户 shell 中执行:
$ systemctl --user daemon-reload
$ systemctl --user start postgres-18-alpine.service不要使用 systemctl --user enable。Quadlet 生成的是瞬态服务,开机自启由 [Install] WantedBy=default.target 控制。
查看状态与日志:
$ systemctl --user status postgres-18-alpine.service
$ journalctl --user -u postgres-18-alpine.service -f验证开机自启:重启系统后不登录 podman 用户,从 root 检查:
# run0 -u podman systemctl --user is-active postgres-18-alpine.service
# run0 -u podman podman ps --filter name=postgres-18-alpine9. 删除 Quadlet 容器
在 podman 用户 shell 中优先使用:
$ podman quadlet rm postgres-18-alpine.container常用选项:
$ podman quadlet rm --force postgres-18-alpine.container
$ podman quadlet rm --ignore postgres-18-alpine.container
$ podman quadlet rm --all手动方式:
$ systemctl --user stop postgres-18-alpine.service
$ rm ~/.config/containers/systemd/postgres-18-alpine.container
$ systemctl --user daemon-reload
$ podman rm -f postgres-18-alpine注意:删除 Quadlet 不会删除数据卷。彻底清除数据需在 podman 用户 shell 中执行:
$ podman volume rm postgres-data10. 升级 PostgreSQL
10.1. 小版本升级
同一主要版本内的小版本升级,建议使用 Drop-in 覆盖镜像,便于回滚。在 podman 用户 shell 中:
$ mkdir -p ~/.config/containers/systemd/postgres-18-alpine.container.d/创建 10-image.conf:
[Container]
Image=docker.io/library/postgres:18.2-alpine升级前备份:
$ podman exec postgres-18-alpine pg_dump -U postgres -Fc your_database > backup.dump执行升级:
$ systemctl --user daemon-reload
$ systemctl --user restart postgres-18-alpine.service由于使用固定 UIDMap / GIDMap,替换镜像触发重建时映射保持不变,不会出现数据卷权限漂移。
11. 故障排查
11.1. Quadlet 服务未生成
在 podman 用户 shell 中执行:
$ systemctl --user daemon-reload
$ systemctl --user list-unit-files | grep postgres
$ systemctl --user cat postgres-18-alpine.service使用生成器 dry-run:
$ /usr/lib/systemd/system-generators/podman-system-generator --user --dryrun或:
$ /usr/libexec/podman/quadlet -dryrun -user查看生成器日志:
$ journalctl --user -t quadlet-generator11.2. 服务启动失败
$ systemctl --user status postgres-18-alpine.service
$ journalctl --user -u postgres-18-alpine.service -n 100 --no-pager
$ journalctl --user -u postgres-18-alpine.service -f常见错误:
| 现象 | 原因 | 处理 |
|---|---|---|
no subuid ranges found | 未配置 subuid/subgid | 执行 usermod --add-subuids ... |
/run/user/0/libpod permission denied | 使用了裸 su podman | 改用 run0 -u podman |
Failed to connect to user scope bus | 缺少 D-Bus/XDG 环境 | 改用 run0 -u podman |
Unit not found | Quadlet 未生成 | 检查文件路径、命名,执行 daemon-reload 和 dry-run |
Permission denied 访问数据卷 | UID 映射变化或不匹配 | 使用固定 UIDMap / GIDMap |
重启后未自启 | 未启用 linger | loginctl enable-linger podman |
systemctl enable 报 transient | Quadlet 服务不能 enable | 由 [Install] 控制自启 |
12. 小结
Debian 上手动创建专用 podman 用户,并配置 /etc/subuid、/etc/subgid。
为 podman 用户启用 linger:loginctl enable-linger podman。
使用 run0 -u podman 进入用户环境,不要使用裸 su podman。
Quadlet 文件放在 ~/.config/containers/systemd/。
使用 systemctl --user daemon-reload 和 start,不要使用 enable。
PostgreSQL 使用固定 UIDMap / GIDMap,不使用 UserNS=auto 或 keep-id。
删除 Quadlet 容器优先使用 podman quadlet rm,注意数据卷不会自动删除。
小版本升级用 Drop-in 覆盖 Image=,升级前备份。
故障排查优先使用 journalctl --user -u <service> 和 Quadlet dry-run。