OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 122

Pods and Kubernetes manifests

Organize local containers that share networking, and check the scope of Kubernetes manifest imports and exports.

English translation · Code and commands follow the Chinese original.

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

Podman pods let local containers share selected namespaces and provide a way to organize Kubernetes manifest workflows.


What is a pod? (A beginner’s introduction)#

Start with a scenario

Imagine deploying a website that needs three components: a web server, a backend API, and a database.

The Docker approach (independent containers):

容器 A (nginx)     独立网络地址(示意)
容器 B (api)       独立网络地址(示意)
容器 C (postgres)  独立网络地址(示意)

Separate network namespaces often communicate through a bridge network and container DNS. Actual overhead depends on the network implementation and workload; this alone does not mean that a database on the same machine must share a pod with the API.

The pod approach (sharing the same "room"):

┌──────── Pod 共享网络 ────────┐
│  localhost                  │
│  容器 A (nginx)              │  ← 通过 localhost:3000 访问 API
│  容器 B (api)                │  ← 通过 localhost:5432 访问 DB
│  容器 C (postgres)           │
└──────────────────────────────┘

A pod’s core feature: containers in the same pod share networking. They can communicate with each other through localhost without exposing ports externally.

How pods relate to Kubernetes

In Kubernetes, a pod is the smallest scheduling unit: you cannot run a container directly; it must be placed in a pod. Podman brings the pod concept to local development so you can use the same organizational model on a single machine.


Basic pod operations#

Create an empty pod

# 创建 Pod 并暴露端口
podman pod create --name mypod -p 127.0.0.1:8080:80
#                └───────┘ └────────┘
#               Pod 名称    把 Pod 的 80 端口映射到宿主机 8080

# 查看所有 Pod
podman pod ps
# 输出类似:
# POD ID     NAME   STATUS  CREATED  INFRA ID   # OF CONTAINERS
# abc123...  mypod  Running 2s ago   def456...  1
# 注意:刚创建的 Pod 已经有 1 个容器——这就是 infra 容器

What is an infra container?

When creating a pod, Podman creates an infra container by default. It acts like the pod’s "landlord", holding shared namespaces such as networking and IPC. Other containers "move into" this landlord’s house when they join the pod.

# 查看 infra 容器(podman pod ps 中 INFRA ID 对应的容器)
podman ps --pod
# infra 容器名字类似 abc123-infra,状态为 Running

# 当前默认由 Podman 创建本地轻量 infra 镜像;可用 --infra-image 覆盖

Add containers to a pod

# 创建 Pod
podman pod create --name webapp -p 127.0.0.1:8080:80

# 向 Pod 中添加 nginx 容器
podman run -d --pod webapp --name web docker.io/library/nginx:alpine
#              └───────┘ └──────┘
#           加入 webapp Pod  容器名

# 添加 API 容器(同一 Pod 内可用 localhost 访问 nginx)
podman run -d --pod webapp --name api myapi:latest

# 添加数据库容器
podman run -d --pod webapp --name db \
  --env-file /path/to/private/postgres.env \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16-alpine

The key point: the api container can now use localhost:80 to access web, and web can use localhost:3000 to access api, all within the shared network namespace. Services must listen on the corresponding ports, and ports must not conflict.

Manage a pod

# 查看 Pod 内所有容器
podman pod ps --ctr-names

# 查看 Pod 详情
podman pod inspect webapp

# 停止整个 Pod(停掉里面所有容器)
podman pod stop webapp

# 启动整个 Pod
podman pod start webapp

# 重启整个 Pod
podman pod restart webapp

# 暂停/恢复 Pod
podman pod pause webapp
podman pod unpause webapp

# 删除 Pod(和里面所有容器)
podman pod rm -f webapp

Bidirectional conversion: Podman ↔ Kubernetes#

This is one of Podman’s standout capabilities.

Generate Kubernetes YAML from a pod

# 生成 K8s YAML
podman kube generate webapp > webapp.yaml

# 产出后检查镜像、环境变量、卷、hostPath、标签和网络字段
# 迁移集群前调整清单;经授权部署时才运行 kubectl apply -f webapp.yaml

Example generated YAML (simplified):

apiVersion: v1
kind: Pod
metadata:
  name: webapp
spec:
  containers:
    - name: web
      image: docker.io/library/nginx:alpine
      ports:
        - containerPort: 80
    - name: db
      image: docker.io/library/postgres:16-alpine
      env:
        - name: POSTGRES_PASSWORD
          value: NON_SECRET_EXAMPLE_REPLACE_BEFORE_USE

Run local containers from Kubernetes YAML

# 手头有个 K8s YAML,想本地跑起来看看
podman kube play webapp.yaml

# 本地解释支持的 Kubernetes 对象/字段,不是完整 Kubernetes 控制平面
# Podman 会解析 YAML,在本地创建对应的 Pod 和容器

Generate systemd service files

# 为当前运行的 Pod 生成 systemd unit file
podman generate systemd --files --name webapp --pod-prefix myapp
# --files 输出到当前目录;--name 使用资源名;webapp 是位置参数;--pod-prefix 控制前缀

# 但官方现在更推荐用 Quadlet(见下一篇)

Practical example: WordPress + MySQL in a pod#

# 1. 创建 Pod
podman pod create --name wordpress -p 127.0.0.1:8080:80

# 2. 启动 MariaDB(WordPress 的数据库)
podman run -d \
  --pod wordpress \
  --name wp-db \
  --env-file /path/to/private/mariadb.env \
  -e MYSQL_DATABASE=wordpress \
  -e MYSQL_USER=wpuser \
  -v wp-db:/var/lib/mysql \
  docker.io/library/mariadb:11

# 3. 启动 WordPress
podman run -d \
  --pod wordpress \
  --name wp-app \
  -e WORDPRESS_DB_HOST=127.0.0.1 \
  -e WORDPRESS_DB_USER=wpuser \
  --env-file /path/to/private/wordpress.env \
  -e WORDPRESS_DB_NAME=wordpress \
  docker.io/library/wordpress:latest

# 4. 打开 http://localhost:8080,WordPress 安装界面就绪

# 5. 生成 K8s YAML 存档
podman kube generate wordpress > wordpress-k8s.yaml

Compared with Docker Compose: Compose’s depends_on controls dependencies, not addressing; services on the same network are usually resolved by service name. Pods can use localhost, but traffic still passes through the network stack, so the overhead is not zero. MariaDB initialization and WordPress startup still need readiness retries.


Shared storage in pods#

Containers in the same pod can share volumes, but each must explicitly mount the same path:

podman run -d --pod mypod --name container-a -v shared:/data app-a:latest
podman run -d --pod mypod --name container-b -v shared:/data app-b:latest
# container-a 和 container-b 都能读写 shared 卷里的文件


The example env files are stored outside the repository with restricted permissions and provide required values such as matching database passwords; all strings shown are invalid demonstration values. Exported kube manifests may contain plaintext environment variables and must not be uploaded as though they were secure archives. podman play kube is an alias for podman kube play, and generate kube is the old entry point; renamed commands do not make the functionality a complete Kubernetes implementation. The namespaces shared by a pod are configurable; shared networking does not automatically share filesystems or volumes. podman generate systemd is deprecated and retained here only to identify older workflows. Use Quadlet for new services.

References & further reading

  1. Official Podman run manual ↗
  2. Official Podman overview and rootless mode ↗
  3. Pod creation and infra ↗
  4. Scope of kube play ↗
  5. kube generate ↗
  6. Legacy systemd generation command ↗

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 ARTICLEPods and multi-service configuration with Quadlet

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