OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 123

Pods and multi-service configuration with Quadlet

Add a complete dependency template for Pod, database, API, and proxy resources.

English translation · Code and commands follow the Chinese original.

IN THESE READING PATHSPodman container workflows ↗
About 3 min readStart reading

First read Quadlet Service Lifecycle for generation, loading, startup, logs, persistence, and boot-time requirements. This article provides in-depth examples of Pods and complete multi-service configurations.

Environment variables and sensitive information#

[Container]
Image=docker.io/library/mariadb:11
ContainerName=mariadb

EnvironmentFile=%h/.config/private/mariadb.env
Environment=MYSQL_DATABASE=myapp
Environment=MYSQL_USER=user
# 每行一个环境变量,等于 podman run -e KEY=VALUE

# 安全做法:从文件加载敏感信息
# 受限文件提供 MYSQL_ROOT_PASSWORD 与 MYSQL_PASSWORD,避免明文写进资源源文件
# %h = 用户家目录

Networking and ports

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

# 映射到宿主机指定 IP(只本机可访问)
PublishPort=127.0.0.1:8080:80

# 替代示例:省略宿主IP会发布所有接口,需要明确的访问与防火墙政策
# PublishPort=8080:80

# 加入自定义网络
Network=mynet.network

# 或加入 Pod
Pod=myapp.pod

.pod files: declarative Pod definitions#

~/.config/containers/systemd/myapp.pod:

[Pod]
PodName=myapp
PublishPort=127.0.0.1:8080:80
# Pod 暴露的端口,里面所有容器共享

Reference a Pod in a .container file:

[Container]
Image=docker.io/library/nginx:alpine
ContainerName=web
Pod=myapp.pod
# 这个容器加入 myapp Pod,共享网络

A multi-container application: database, API, and proxy#

This example uses an Nginx reverse proxy + Python API + PostgreSQL.

File structure

~/.config/containers/systemd/
├── appnet.network       # 共享网络
├── pgdata.volume        # PostgreSQL 数据卷
├── app-pg.container     # PostgreSQL 容器
├── app-api.container    # Python API 容器
└── app-web.container    # Nginx 反向代理

appnet.network

[Network]
Driver=bridge

pgdata.volume

[Volume]

app-pg.container

[Unit]
Description=App PostgreSQL
# After= 和 Requires= 是 systemd 依赖声明
# After=   → 启动顺序(xx 先启动)
# Requires= → 强依赖(xx 启动失败则本服务不会启动)

[Container]
ContainerName=app-pg
Image=docker.io/library/postgres:16-alpine
Network=appnet.network
Volume=pgdata.volume:/var/lib/postgresql/data:Z

EnvironmentFile=%h/.config/private/postgres.env
Environment=POSTGRES_DB=appdb

# 健康检查:用 pg_isready 确认数据库就绪
HealthCmd=pg_isready -U postgres || exit 1
Notify=healthy
HealthInterval=10s
HealthStartPeriod=30s
# HealthStartPeriod — 早期失败通常不计入重试阈值,不是完全停止检查

[Service]
Restart=on-failure
# on-failure — 只在异常退出时重启(exit code ≠ 0 时)

[Install]
WantedBy=default.target

app-api.container

[Unit]
Description=App API Server
After=app-pg.service
Requires=app-pg.service
# After 只按 unit 状态排序;这里配合 PG 的 Notify=healthy 等待有效检查
# Requires 与 After 组合拉起依赖并约束启动失败;不等于持续应用健康依赖

[Container]
ContainerName=app-api
Image=registry.example.com/demo/myapi:latest
Network=appnet.network
PublishPort=127.0.0.1:3000:3000

EnvironmentFile=%h/.config/private/api.env
# 文件中提供与数据库匹配的 DATABASE_URL;示例主机字段为 app-pg
# 用容器名 app-pg 连接 PG,因为它们在同一个自定义网络中

HealthCmd=curl -f http://localhost:3000/health || exit 1
HealthInterval=15s

[Service]
Restart=on-failure

[Install]
WantedBy=default.target

app-web.container

[Unit]
Description=App Nginx Proxy
After=app-api.service
Requires=app-api.service

[Container]
ContainerName=app-web
Image=docker.io/library/nginx:alpine
Network=appnet.network
PublishPort=127.0.0.1:8080:80

Volume=%h/.config/nginx/app.conf:/etc/nginx/conf.d/default.conf:ro,Z
# %h = 用户家目录,ro = 只读挂载

[Service]
Restart=on-failure

[Install]
WantedBy=default.target

Deployment

# 把所有文件放入目录
ls ~/.config/containers/systemd/

# 一键加载并启动
systemctl --user daemon-reload
systemctl --user start app-web   # 启动最外层
# systemd 自动按依赖顺序:先启动 PG → 再 API → 最后 Nginx

# 各源文件通过 [Install] 声明目标关联;未登录启动另需管理员linger政策
# 不 enable 生成 unit;各源文件 [Install] 的 WantedBy 由 generator 处理

# 查看整体状态
systemctl --user status app-pg app-api app-web

Template prerequisites and boundaries#

These code blocks represent separate files. The image registry.example.com/demo/myapi:latest is a nonfunctional placeholder that must be replaced; the API image must provide curl, a health endpoint, and database connection retries. Prepare the Nginx configuration file yourself, pointing its upstream to app-api:3000; the example does not present an unspecified application as a ready-to-deploy product. EnvironmentFile must exist, have restricted permissions, and contain variables only in the application’s required format. Keep it out of git/the Nix store; environment variables themselves can also be read by processes with permission to inspect them.

.volume references give data an independent lifecycle, while .network provides name resolution. Use SELinux’s Z only for dedicated directories. Pod files suit containers that need to share localhost; do not mechanically mix them into an existing bridge-based multi-service design, since containers in the same Pod share ports and can conflict. HealthCmd runs inside the container, and Notify=healthy requires support in the installed version; design application readiness and ongoing health recovery separately.

For automatic updates, linger, generator checks, and service removal, refer to the lifecycle article. Do not run enable on generated services.

References & further reading

  1. Official Podman run Manual ↗
  2. Official Podman Overview and Rootless Documentation ↗
  3. Official Quadlet Syntax ↗
  4. Quadlet Container Health and Notifications ↗

Adapted from technical notes. Examples use generic names.

Continue along a reading path

Podman container workflows

  1. 01Podman fundamentals
  2. 02A basic Podman usage guide
  3. 03Rootless containers: whose file permissions count?
  4. 04Rootless configuration: mappings, ICMP, and registries
  5. 05Pods and Kubernetes manifests
  6. 06How does Quadlet turn container declarations into services?
  7. 07Pods and multi-service configuration with Quadlet
  8. 08Compose providers and interoperability with Docker tools
  9. 09Managing Podman on NixOS
  10. 10Advanced Podman operations and tuning
  11. 11Docker and Podman: architecture, compatibility, and coexistence
CONTINUE READING / NEXT ARTICLECompose providers and interoperability with Docker tools

Separate the responsibilities of Compose implementations, the Podman wrapper, and the compatible API socket.