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-overlayfs
Note

此处及下文代码块中的指令,以 # 开头的需 root 权限执行(普通用户需要添加 sudo),以 $ 开头的则由 podman 用户执行。

3. 创建非 root 用户

3.1. 创建 podman 用户

# useradd -m -s /bin/bash podman

3.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:65536

4. 启用 Linger

Rootless 用户的 systemd 用户实例默认在用户登录后才启动。必须启用 linger,容器才能在系统启动时无人值守运行。

# loginctl enable-linger podman

验证:

# loginctl show-user podman --property=Linger
Linger=yes

5. 切换到 podman 用户

5.1. 推荐方式:run0

从 root 切换到 podman 用户,推荐使用 run0:

# run0 -u podman

run0 会建立完整的用户会话环境,正确设置 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.service

6.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_map

7.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-alpine

9. 删除 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-data

10. 升级 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-generator

11.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。