Skip to content

필사 모드: Docker 与 Podman 命令完全指南:把容器运维的一切整理在一页之内

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

1. 引言:容器生态系统的两大阵营

1.1 Docker 的历史与当下地位

2013 年 Solomon Hykes 在 PyCon 上用一场 5 分钟的闪电演讲「The future of Linux Containers」把 Docker 公之于众。它带动了容器技术的普及,并彻底革新了软件的交付方式。在 Docker 之前,LXC(Linux Containers)、FreeBSD Jail、Solaris Zones 等容器技术就已经存在,但 Docker 把 镜像分层系统基于 Dockerfile 的声明式构建名为 Docker Hub 的中央registry 结合起来,把「Build once, run anywhere」的愿景变成了现实。

Docker 后来成为 OCI(Open Container Initiative)标准的基础,也坐稳了 Kubernetes 生态核心运行时的位置。然而 2020 年 Kubernetes 宣布弃用 dockershim,加上 Docker Desktop 商业授权政策变更(250 人以上企业需付费),业界开始寻找替代方案。

目前 Docker 依然是开发环境的事实标准(de facto standard),Docker Hub 也仍是全球最大的容器镜像仓库。但在生产环境中,containerd、CRI-O 以及 Podman 正在快速扩大自己的地盘。

1.2 Podman 登场的背景

Podman(Pod Manager)是由 Red Hat 主导开发的 兼容 OCI(Open Container Initiative)的容器引擎。Red Hat 判断 Docker 的单一守护进程(daemon)架构在安全性、稳定性和系统集成方面存在根本性局限,于是开发了 Podman、Buildah、Skopeo 三款工具来解决这些问题。

  • Podman:容器的运行与管理(替代 docker CLI)
  • Buildah:专注于镜像构建(替代 docker build)
  • Skopeo:镜像复制、检查、签名(跨registry的镜像管理)

这三款工具各自独立工作,同时又互为补充。这是一种忠实遵循 Unix 哲学「一个工具把一件事做好」的设计。

1.3 为什么选择 Podman?

需要考虑从 Docker 迁移到 Podman 的核心理由如下。

Daemonless Architecture:Docker 始终需要在后台运行 dockerd 守护进程。一旦这个守护进程挂掉,所有容器管理都无法进行(Single Point of Failure)。Podman 没有守护进程,直接对每个容器执行 fork-exec,因此不存在这个问题。

Rootless Containers:Docker 虽然也支持 rootless 模式,但 Podman 从一开始就把 rootless 作为基本设计原则。普通用户无需 root 权限即可运行容器,安全性大幅提升。

Fork-Exec Model:由于遵循传统的 Unix 进程模型,与 systemd、audit、cgroup 的集成非常自然。每个容器都作为 Podman 进程的子进程存在。

Pod 原生支持:可以在本地直接使用 Kubernetes 的 Pod 概念,还能用 podman generate kube 命令自动生成 Kubernetes YAML。

CLI 兼容性:只要设置 alias docker=podman,大部分 Docker 命令都可以照原样使用。


2. 架构对比:Docker vs Podman

2.1 Docker 架构:Client-Daemon 模型

Docker 采用典型的 客户端-服务器架构。当用户执行 docker run 时,Docker CLI(客户端)通过 REST API 向 Docker Daemon(dockerd)发送请求。Daemon 把请求委托给 containerdcontainerd 再调用 runc 来真正创建容器。

┌─────────────────────────────────────────────────────────────────┐
Docker Architecture├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────┐    REST API     ┌──────────────────┐             │
│  │  Docker   │───────────────▶│   Docker Daemon   │             │
│  │   CLI/var/run/         (dockerd)      │             │
│  │          │  docker.sock    │                    │             │
│  └──────────┘                 │  ┌──────────────┐ │             │
│                               │  │  containerd   │ │             │
│                               │  │              │ │             │
│                               │  │  ┌────────┐ │ │             │
│                               │  │  │  runc   │ │ │             │
│                               │  │   (OCI)   │ │ │             │
│                               │  │  └────┬───┘ │ │             │
│                               │  └───────┼─────┘ │             │
│                               └──────────┼───────┘             │
│                                          │                      │
│                    ┌─────────────────────┼──────────────────┐   │
│                    │    Container 1Container 2      │   │
    (process)          (process)        │   │
│                    └─────────────────────┴──────────────────┘   │
│                                                                 │
│  ⚠ dockerd 挂掉则所有容器无法管理 (SPOF)│  ⚠ 访问 docker.sock = 可获得 root 权限                          │
└─────────────────────────────────────────────────────────────────┘

这种结构的核心问题在于,docker.sock 的访问权限事实上等同于 root 权限。属于 docker 组的用户可以挂载宿主机文件系统,或用 --privileged 标志运行容器,从而完全控制宿主机。

2.2 Podman 架构:Daemonless Fork-Exec 模型

Podman 没有守护进程。执行 podman run 时,Podman 进程直接 fork 出 conmon(Container Monitor),conmon 再运行 OCI 运行时(crunrunc)来创建容器。

┌─────────────────────────────────────────────────────────────────┐
Podman Architecture├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────┐  direct fork-exec   ┌──────────┐                │
│  │  Podman   │────────────────────▶│  conmon   │                │
│  │   CLI        (no daemon!)      (container │                │
│  │          │                      │ monitor)  │                │
│  └──────────┘                      └─────┬────┘                │
│                                          │                      │
│                                    ┌─────▼────┐                │
│                                    │   crun    │                │
  (OCI     │                │
│                                    │ runtime)  │                │
│                                    └─────┬────┘                │
│                                          │                      │
│                    ┌─────────────────────┼──────────────────┐   │
│                    │    Container 1Container 2      │   │
    (child proc)       (child proc)     │   │
│                    └─────────────────────┴──────────────────┘   │
│                                                                 │
│  ✅ 无守护进程 → 无 SPOF│  ✅ 每个容器都是独立进程 → 易于与 systemd 集成                  │
│  ✅ 默认支持 Rootless└─────────────────────────────────────────────────────────────────┘

conmon 是一个轻量级监控进程,它捕获容器的 stdout/stderr,记录容器的 exit code,并在 Podman CLI 退出之后继续监视容器。

2.3 核心对比表

对比项DockerPodman
架构Client-Daemon (dockerd)Daemonless (fork-exec)
默认运行权限root (daemon)rootless(普通用户)
OCI 运行时runccrun(默认),支持 runc
镜像构建内置 (BuildKit)联动 Buildah
守护进程必需 (dockerd + containerd)
systemd 集成有限原生 (Quadlet)
Pod 支持无(用 Compose 替代)原生支持
生成 K8s YAMLpodman generate kube
运行 K8s YAMLpodman play kube
基于 Socket 的 APIdocker.sock(始终启用)podman.sock(按需启用)
容器监控器containerd-shimconmon
默认registry仅 docker.io多registry搜索
Compose 支持Docker Compose(官方)podman-compose / podman compose
许可证Apache 2.0 + 商业 (Desktop)Apache 2.0(完全开源)
安全框架AppArmor/SeccompSELinux/AppArmor/Seccomp

3. 安装与初始配置

3.1 安装 Docker

Ubuntu / Debian

# 移除已有的 Docker 软件包
sudo apt-get remove docker docker-engine docker.io containerd runc

# 安装必需软件包
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release

# 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# 添加 Docker 软件源
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

# 把当前用户加入 docker 组(需要重新登录)
sudo usermod -aG docker $USER

# 启动服务并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker

# 确认安装
docker version
docker run hello-world

CentOS / RHEL / Rocky Linux

# 移除已有软件包
sudo yum remove -y docker docker-client docker-client-latest \
  docker-common docker-latest docker-latest-logrotate \
  docker-logrotate docker-engine

# 添加 Docker 软件源
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo \
  https://download.docker.com/linux/centos/docker-ce.repo

# 安装 Docker Engine
sudo yum install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

# 启动服务
sudo systemctl start docker
sudo systemctl enable docker

# 添加用户组
sudo usermod -aG docker $USER

macOS (Docker Desktop)

# 通过 Homebrew 安装
brew install --cask docker

# 或者从 Docker Desktop 官网下载 .dmg
# https://www.docker.com/products/docker-desktop/

# 安装后启动 Docker Desktop 应用 → Docker Engine 自动启动
docker version

3.2 安装 Podman

Ubuntu / Debian

# Ubuntu 22.04+ 的默认软件源中已包含
sudo apt-get update
sudo apt-get install -y podman

# 需要最新版本时 (Ubuntu)
sudo mkdir -p /etc/apt/keyrings
curl -fsSL "https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_$(lsb_release -rs)/Release.key" \
  | gpg --dearmor \
  | sudo tee /etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg > /dev/null
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg] \
  https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_$(lsb_release -rs)/ /" \
  | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list > /dev/null
sudo apt-get update
sudo apt-get install -y podman

# 确认安装
podman version
podman info

CentOS / RHEL / Rocky Linux

# RHEL 8+ / CentOS Stream 8+ 默认已包含
sudo dnf install -y podman

# 安装附加工具
sudo dnf install -y buildah skopeo podman-compose

# Podman 没有守护进程,因此不需要 systemctl start!
podman version

macOS (Podman Machine)

# 通过 Homebrew 安装
brew install podman

# 初始化 Podman Machine(创建 Linux VM)
podman machine init

# 启动 Machine
podman machine start

# 确认安装
podman version
podman machine list

# 停止 Machine
podman machine stop

# 删除 Machine
podman machine rm

参考:在 macOS 和 Windows 上,Podman 会创建一个轻量级 Linux VM(基于 QEMU 或 Apple Hypervisor Framework),并在其中运行容器。方式与 Docker Desktop 类似,但还额外提供了名为 Podman Desktop 的免费 GUI 工具。

3.3 兼容性配置:alias docker=podman

要让既有的 Docker 脚本和工作流不做任何改动就能继续使用,只需简单设置一个 alias 就够了。

# 添加到 shell 配置文件 (~/.bashrc 或 ~/.zshrc)
alias docker=podman
alias docker-compose=podman-compose

# 立即生效
source ~/.bashrc  # 或 source ~/.zshrc

# RHEL/CentOS 提供 podman-docker 软件包
sudo dnf install -y podman-docker
# 该软件包会创建 /usr/bin/docker → /usr/bin/podman 的符号链接
# 同时也包含 docker.sock 的模拟

3.4 Docker Desktop vs Podman Desktop

对比项Docker DesktopPodman Desktop
许可证250 人以上企业收费 ($5+/user/month)完全免费 (Apache 2.0)
GUI功能丰富的 UI基础 UI(正在快速改进)
ExtensionDocker Extensions 市场支持 Extension
Kubernetes内置 K8s 集群联动 Kind/Minikube
VM 后端Linux Kit (macOS), WSL2 (Windows)QEMU / Apple Hypervisor
资源管理CPU、Memory、Disk 设置podman machine init --cpus --memory

4. 镜像管理命令

容器由镜像创建而来。镜像管理是容器运维中基础中的基础。

4.1 搜索镜像

# 在 Docker Hub 上搜索镜像
docker search nginx
podman search nginx

# 限制结果数量
docker search --limit 5 nginx
podman search --limit 5 nginx

# 只搜索官方镜像 (Docker)
docker search --filter is-official=true nginx

# Podman 会同时搜索多个registry(取决于 registries.conf 的配置)
# /etc/containers/registries.conf
# unqualified-search-registries = ["docker.io", "quay.io", "ghcr.io"]
podman search --list-tags docker.io/library/nginx

4.2 拉取镜像 (Pull)

# 下载默认镜像
docker pull nginx
podman pull nginx

# 指定特定标签
docker pull nginx:1.25-alpine
podman pull nginx:1.25-alpine

# 从特定registry拉取
docker pull ghcr.io/myorg/myapp:latest
podman pull quay.io/prometheus/prometheus:latest

# 指定特定平台(架构)
docker pull --platform linux/arm64 nginx:latest
podman pull --platform linux/arm64 nginx:latest

# 下载所有标签
docker pull --all-tags nginx
podman pull --all-tags nginx

# 用 Digest 指定特定构建(不可变引用)
docker pull nginx@sha256:abc123...
podman pull nginx@sha256:abc123...

4.3 查看镜像列表

# 本地镜像列表
docker images
podman images

# 详细形式(等价命令)
docker image ls
podman image ls

# 过滤特定镜像
docker images nginx
podman images nginx

# 只显示 dangling 镜像(无标签的镜像)
docker images -f dangling=true
podman images -f dangling=true

# 只输出镜像 ID
docker images -q
podman images -q

# 自定义格式输出
docker images --format "{{.Repository}}:{{.Tag}} - {{.Size}}"
podman images --format "{{.Repository}}:{{.Tag}} - {{.Size}}"

# JSON 格式输出 (Podman)
podman images --format json

4.4 镜像详细信息 (Inspect)

# 查看镜像详细信息
docker inspect nginx:latest
podman inspect nginx:latest

# 只提取特定字段 (Go template)
docker inspect --format '{{.Config.ExposedPorts}}' nginx
podman inspect --format '{{.Config.ExposedPorts}}' nginx

# 查看镜像大小
docker inspect --format '{{.Size}}' nginx
podman inspect --format '{{.Size}}' nginx

# 查看环境变量
docker inspect --format '{{.Config.Env}}' nginx
podman inspect --format '{{.Config.Env}}' nginx

# 查看 Entrypoint 与 Cmd
docker inspect --format '{{.Config.Entrypoint}} {{.Config.Cmd}}' nginx
podman inspect --format '{{.Config.Entrypoint}} {{.Config.Cmd}}' nginx

4.5 镜像历史 (History)

# 查看镜像分层历史
docker history nginx
podman history nginx

# 显示完整命令(不截断)
docker history --no-trunc nginx
podman history --no-trunc nginx

# JSON 格式 (Podman)
podman history --format json nginx

4.6 镜像标签 (Tag)

# 给镜像添加新标签
docker tag nginx:latest myregistry.com/nginx:v1.0
podman tag nginx:latest myregistry.com/nginx:v1.0

# 添加多个标签
docker tag myapp:latest myapp:v2.1.0
docker tag myapp:latest myapp:stable
podman tag myapp:latest myapp:v2.1.0
podman tag myapp:latest myapp:stable

4.7 删除镜像 (Remove)

# 删除镜像
docker rmi nginx:latest
podman rmi nginx:latest

# 等价命令(推荐)
docker image rm nginx:latest
podman image rm nginx:latest

# 强制删除(即使有正在运行的容器)
docker rmi -f nginx:latest
podman rmi -f nginx:latest

# 删除所有镜像
docker rmi $(docker images -q)
podman rmi -a

# 只删除 Dangling 镜像
docker image prune -f
podman image prune -f

# 删除所有未被使用的镜像
docker image prune -a -f
podman image prune -a -f

# 按特定条件过滤后删除(超过 24 小时的镜像)
docker image prune -a --filter "until=24h"
podman image prune -a --filter "until=24h"

4.8 镜像保存/加载 (Save/Load)

在气隙(air-gapped)环境或离线传输时非常有用。

# 把镜像保存为 tar 文件
docker save -o nginx.tar nginx:latest
podman save -o nginx.tar nginx:latest

# 把多个镜像保存到一个 tar 中
docker save -o images.tar nginx:latest redis:latest postgres:15
podman save -o images.tar nginx:latest redis:latest postgres:15

# 以 gzip 压缩保存
docker save nginx:latest | gzip > nginx.tar.gz
podman save nginx:latest | gzip > nginx.tar.gz

# 从 tar 文件加载镜像
docker load -i nginx.tar
podman load -i nginx.tar

# 从 gzip 压缩文件加载
docker load -i nginx.tar.gz
podman load -i nginx.tar.gz

4.9 镜像 Import/Export

save/load 会包含镜像元数据(分层、标签、历史),而 export/import 只处理容器的文件系统。

# 把容器的文件系统导出为 tar
docker export my-container -o container-fs.tar
podman export my-container -o container-fs.tar

# 从 tar 文件创建镜像
docker import container-fs.tar myimage:imported
podman import container-fs.tar myimage:imported

# 直接从 URL 导入
docker import https://example.com/rootfs.tar.gz myimage:latest
podman import https://example.com/rootfs.tar.gz myimage:latest

4.10 registry登录/推送

# 登录 Docker Hub
docker login
podman login docker.io

# 登录特定registry
docker login ghcr.io
podman login quay.io

# 直接指定用户名/密码
docker login -u username -p password registry.example.com
podman login -u username -p password registry.example.com

# 登出
docker logout
podman logout docker.io

# 推送镜像
docker push myregistry.com/myapp:v1.0
podman push myregistry.com/myapp:v1.0

# 推送所有标签
docker push --all-tags myregistry.com/myapp
podman push --all-tags myregistry.com/myapp

5. 镜像构建

5.1 Dockerfile vs Containerfile

Docker 使用 Dockerfile 这个名字,而 Podman/Buildah 默认识别 Containerfile。不过 Podman 也会自动识别 Dockerfile,所以不用改名就能直接使用。内容和语法完全相同。

# Dockerfile 或 Containerfile —— 语法相同
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
USER node
CMD ["node", "dist/main.js"]
项目DockerfileContainerfile
使用工具DockerPodman, Buildah
语法相同相同
默认文件名DockerfileContainerfile
互相兼容Podman 能识别 DockerfileDocker 不识别 Containerfile
指定构建docker build -f Containerfile .podman build -f Dockerfile .

5.2 docker build vs podman build

# 基本构建
docker build -t myapp:latest .
podman build -t myapp:latest .

# 指定特定 Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
podman build -f Containerfile.prod -t myapp:prod .

# 传递 Build argument
docker build --build-arg NODE_ENV=production -t myapp:prod .
podman build --build-arg NODE_ENV=production -t myapp:prod .

# 不使用缓存构建
docker build --no-cache -t myapp:latest .
podman build --no-cache -t myapp:latest .

# 指定目标阶段(多阶段)
docker build --target builder -t myapp:builder .
podman build --target builder -t myapp:builder .

# 多平台构建 (Docker BuildKit)
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .

# Podman 多平台构建
podman build --platform linux/amd64,linux/arm64 --manifest myapp:latest .

# 构建时限制内存
docker build --memory 2g -t myapp:latest .

# 最小化构建上下文大小 —— 必须善用 .dockerignore
# .dockerignore(或 .containerignore)
# node_modules
# .git
# *.md
# dist
# .env

5.3 多阶段构建 (Multi-stage Build) 实战示例

多阶段构建通过分离构建环境与运行环境,把最终镜像体积大幅缩小,是一项核心技巧。

Go 应用示例

# ============================================
# Stage 1: 构建环境
# ============================================
FROM golang:1.22-alpine AS builder

# 安装构建所需的工具
RUN apk add --no-cache git ca-certificates

WORKDIR /app

# 先复制依赖定义(利用缓存)
COPY go.mod go.sum ./
RUN go mod download

# 复制源码并构建
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-w -s" -o /app/server ./cmd/server

# ============================================
# Stage 2: 运行环境 (scratch = 空镜像)
# ============================================
FROM scratch

# 复制 CA 证书(用于 HTTPS 通信)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# 只复制二进制文件
COPY --from=builder /app/server /server

# 以非 root 用户运行
USER 65534:65534

EXPOSE 8080
ENTRYPOINT ["/server"]

Java (Spring Boot) 示例

# ============================================
# Stage 1: 构建
# ============================================
FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /app
COPY gradle/ gradle/
COPY gradlew build.gradle.kts settings.gradle.kts ./
RUN ./gradlew dependencies --no-daemon

COPY src/ src/
RUN ./gradlew bootJar --no-daemon -x test

# 生成 JRE 自定义运行时 (jlink)
RUN jlink \
    --add-modules java.base,java.logging,java.sql,java.naming,java.management,java.instrument,java.security.jgss,java.desktop \
    --strip-debug \
    --no-man-pages \
    --no-header-files \
    --compress=zip-6 \
    --output /custom-jre

# ============================================
# Stage 2: 运行(自定义 JRE)
# ============================================
FROM alpine:3.19

COPY --from=builder /custom-jre /opt/java
COPY --from=builder /app/build/libs/*.jar /app/app.jar

ENV JAVA_HOME=/opt/java
ENV PATH="${JAVA_HOME}/bin:${PATH}"

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Python 示例

# ============================================
# Stage 1: 构建依赖
# ============================================
FROM python:3.12-slim AS builder

RUN pip install --no-cache-dir poetry
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# ============================================
# Stage 2: 运行环境
# ============================================
FROM python:3.12-slim

# 只安装必要的系统库
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /install /usr/local
WORKDIR /app
COPY . .

RUN useradd --create-home appuser
USER appuser

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

5.4 Build Cache 策略

# ❌ 低效:源码一改动就要重新执行 npm install
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build

# ✅ 高效:只有 package.json 变化时才重新执行 npm install
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

缓存层顺序原则:把变更频率低的层放在上面,把变更频率高的层放在下面。

变更频率低     ─── FROM (基础镜像)
RUN apt-get install (系统软件包)
COPY package.json (依赖定义)
RUN npm ci (安装依赖)
COPY . . (源代码)
变更频率高     ─── RUN npm run build (构建)

5.5 用 Buildah 做脚本化构建

Buildah 是一款强大的工具,不需要 Dockerfile 也能用 shell 脚本构建镜像。

#!/bin/bash
# buildah-build.sh —— 不用 Dockerfile 构建镜像

# 创建新容器(从空镜像开始)
container=$(buildah from scratch)

# 或者从基础镜像开始
container=$(buildah from alpine:3.19)

# 安装软件包
buildah run $container -- apk add --no-cache nginx

# 复制文件
buildah copy $container ./nginx.conf /etc/nginx/nginx.conf
buildah copy $container ./html /var/www/html

# 配置
buildah config --port 80 $container
buildah config --entrypoint '["/usr/sbin/nginx", "-g", "daemon off;"]' $container
buildah config --author "DevOps Team" $container
buildah config --label maintainer="devops@example.com" $container

# 提交为镜像
buildah commit $container myapp:latest

# 清理
buildah rm $container

# 确认构建结果
buildah images

5.6 用 Skopeo 复制/检查镜像

Skopeo 可以在 不下载镜像的情况下 直接在registry之间复制或检查镜像。

# 在registry之间直接复制镜像(无需本地下载!)
skopeo copy docker://docker.io/nginx:latest docker://quay.io/myorg/nginx:latest

# 检查镜像元数据(无需下载)
skopeo inspect docker://docker.io/library/nginx:latest

# 查看镜像标签列表
skopeo list-tags docker://docker.io/library/nginx

# 把镜像保存到本地目录 (OCI 格式)
skopeo copy docker://nginx:latest oci:./nginx-oci:latest

# 把镜像保存为 tar 文件
skopeo copy docker://nginx:latest docker-archive:./nginx.tar:nginx:latest

# 带认证复制到私有registry
skopeo copy --src-creds user:pass --dest-creds user:pass \
  docker://source-registry.com/app:v1 \
  docker://dest-registry.com/app:v1

# 删除镜像(从registry中)
skopeo delete docker://myregistry.com/myapp:old-tag

6. 容器生命周期命令

6.1 创建容器 (Create)

create 只创建容器而不启动它。之后可以用 start 来启动。

# 创建容器(不启动)
docker create --name my-nginx nginx:latest
podman create --name my-nginx nginx:latest

# 确认已创建的容器
docker ps -a
podman ps -a

# 启动已创建的容器
docker start my-nginx
podman start my-nginx

6.2 运行容器 (Run) —— 全部主要选项整理

run 一次性完成 create + start。它是容器运维中使用频率最高的命令。

# ============================================
# 基本运行
# ============================================
docker run nginx
podman run nginx

# ============================================
# 后台运行 (-d: detach)
# ============================================
docker run -d --name web nginx
podman run -d --name web nginx

# ============================================
# 交互模式 (-it: interactive + tty)
# ============================================
docker run -it ubuntu:22.04 bash
podman run -it ubuntu:22.04 bash

# ============================================
# 退出时自动删除 (--rm)
# ============================================
docker run --rm -it alpine sh
podman run --rm -it alpine sh

# ============================================
# 端口映射 (-p host:container)
# ============================================
docker run -d -p 8080:80 nginx                    # 指定端口
docker run -d -p 80:80 -p 443:443 nginx           # 多端口
docker run -d -p 127.0.0.1:8080:80 nginx          # 指定网络接口
docker run -d -P nginx                             # 随机端口自动映射
podman run -d -p 8080:80 nginx

# ============================================
# 挂载卷 (-v host:container[:options])
# ============================================
docker run -d -v /host/data:/container/data nginx             # Bind mount
docker run -d -v myvolume:/container/data nginx               # Named volume
docker run -d -v /host/data:/container/data:ro nginx          # 只读
docker run -d --mount type=tmpfs,destination=/tmp nginx       # tmpfs
podman run -d -v /host/data:/container/data:Z nginx           # SELinux 标签 (:Z)

# ============================================
# 环境变量 (-e, --env-file)
# ============================================
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql:8
docker run -d -e DB_HOST=db -e DB_PORT=5432 myapp
docker run -d --env-file .env myapp
podman run -d -e MYSQL_ROOT_PASSWORD=secret mysql:8

# ============================================
# 资源限制 (--memory, --cpus)
# ============================================
docker run -d --memory 512m --memory-swap 1g nginx
docker run -d --cpus 1.5 nginx
docker run -d --cpus 2 --memory 1g --memory-reservation 512m myapp
podman run -d --memory 512m --cpus 1.5 nginx

# ============================================
# 重启策略 (--restart)
# ============================================
docker run -d --restart always nginx          # 总是重启
docker run -d --restart unless-stopped nginx  # 手动停止除外都重启
docker run -d --restart on-failure:5 nginx    # 失败时最多重启 5 次
docker run -d --restart no nginx              # 不重启(默认值)
podman run -d --restart always nginx

# ============================================
# 网络设置 (--network)
# ============================================
docker run -d --network my-network nginx
docker run -d --network host nginx            # 直接使用宿主机网络
docker run -d --network none nginx            # 无网络
podman run -d --network my-network nginx

# ============================================
# 共享 PID / IPC 命名空间
# ============================================
docker run -d --pid host nginx                # 宿主机 PID 命名空间
docker run -d --pid container:other nginx     # 共享其他容器的 PID
docker run -d --ipc host nginx                # 共享宿主机 IPC

# ============================================
# 其他实用选项
# ============================================
docker run -d --hostname myhost nginx               # 设置主机名
docker run -d --dns 8.8.8.8 nginx                   # 指定 DNS 服务器
docker run -d --add-host mydb:10.0.0.5 nginx        # 添加 /etc/hosts 条目
docker run -d --workdir /app myapp                   # 工作目录
docker run -d --user 1000:1000 myapp                 # 指定运行用户
docker run -d --read-only myapp                      # 只读文件系统
docker run -d --log-driver json-file \
  --log-opt max-size=10m --log-opt max-file=3 nginx  # 日志驱动设置

6.3 容器列表 (ps)

# 正在运行的容器列表
docker ps
podman ps

# 所有容器(包含已停止的)
docker ps -a
podman ps -a

# 最近创建的 n 个容器
docker ps -n 5
podman ps -n 5

# 只输出容器 ID
docker ps -q
podman ps -q

# 自定义格式
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
podman ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

# 按特定状态过滤
docker ps -f status=exited
docker ps -f status=running
docker ps -f name=web
podman ps -f status=exited

6.4 启动/停止/重启容器

# 启动容器
docker start my-nginx
podman start my-nginx

# 同时启动多个容器
docker start web db redis
podman start web db redis

# 停止容器 (SIGTERM → 10 秒后 SIGKILL)
docker stop my-nginx
podman stop my-nginx

# 指定超时时间(30 秒后强制终止)
docker stop -t 30 my-nginx
podman stop -t 30 my-nginx

# 停止所有正在运行的容器
docker stop $(docker ps -q)
podman stop -a

# 强制终止容器 (SIGKILL)
docker kill my-nginx
podman kill my-nginx

# 发送特定信号
docker kill -s SIGHUP my-nginx
podman kill -s SIGHUP my-nginx

# 重启容器
docker restart my-nginx
podman restart my-nginx

# 重启所有容器
docker restart $(docker ps -q)
podman restart -a

6.5 暂停/恢复容器

# 暂停容器 (SIGSTOP —— 冻结进程)
docker pause my-nginx
podman pause my-nginx

# 解除暂停 (SIGCONT)
docker unpause my-nginx
podman unpause my-nginx

6.6 删除容器

# 删除已停止的容器
docker rm my-nginx
podman rm my-nginx

# 强制删除正在运行的容器
docker rm -f my-nginx
podman rm -f my-nginx

# 连同卷一起删除
docker rm -v my-nginx
podman rm -v my-nginx

# 删除所有已停止的容器
docker container prune -f
podman container prune -f

# 删除所有容器(含运行中的)
docker rm -f $(docker ps -aq)
podman rm -f -a

6.7 容器重命名 / 提交 / 等待

# 重命名容器
docker rename old-name new-name
podman rename old-name new-name

# 把容器保存为镜像(对当前状态做快照)
docker commit my-container myimage:snapshot
podman commit my-container myimage:snapshot

# 提交时添加元数据
docker commit -m "Added config files" -a "Author" my-container myimage:v2
podman commit -m "Added config files" -a "Author" my-container myimage:v2

# 等待容器退出(返回 exit code)
docker wait my-container
podman wait my-container

7. 容器监控与调试

7.1 查看日志 (Logs)

# 输出全部日志
docker logs my-container
podman logs my-container

# 实时日志流 (follow)
docker logs -f my-container
podman logs -f my-container

# 只显示最后 N 行
docker logs --tail 100 my-container
podman logs --tail 100 my-container

# 包含时间戳
docker logs -t my-container
podman logs -t my-container

# 特定时间之后的日志
docker logs --since 2024-01-01T00:00:00 my-container
docker logs --since 30m my-container    # 最近 30 分钟
docker logs --since 2h my-container     # 最近 2 小时
podman logs --since 30m my-container

# 特定时间之前的日志
docker logs --until 2024-01-01T12:00:00 my-container
podman logs --until 2024-01-01T12:00:00 my-container

# 组合:实时 + 最后 50 行 + 时间戳
docker logs -f --tail 50 -t my-container
podman logs -f --tail 50 -t my-container

7.2 实时资源监控 (Stats)

# 实时显示所有运行中容器的资源使用量
docker stats
podman stats

# 只监控特定容器
docker stats my-container
podman stats my-container

# 只输出一次(适合脚本)
docker stats --no-stream
podman stats --no-stream

# 自定义格式
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
podman stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"

输出示例:

NAME        CPU %     MEM USAGE / LIMIT     NET I/O           BLOCK I/O
web         0.50%     45.2MiB / 512MiB      12.5kB / 8.3kB    4.1MB / 0B
db          2.30%     256MiB / 1GiB         45.2kB / 12.1kB   50MB / 120MB
redis       0.10%     12.5MiB / 256MiB      3.2kB / 1.1kB     0B / 0B

7.3 查看进程 (Top)

# 容器内部进程列表
docker top my-container
podman top my-container

# 传递 ps 选项
docker top my-container -aux
podman top my-container -aux

# Podman 专属:支持额外字段
podman top my-container user pid ppid args %cpu %mem
podman top my-container huser hpid

7.4 查看详细信息 (Inspect)

# 容器全部信息 (JSON)
docker inspect my-container
podman inspect my-container

# 查看 IP 地址
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-container
podman inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-container

# 查看挂载信息
docker inspect -f '{{json .Mounts}}' my-container | jq .
podman inspect -f '{{json .Mounts}}' my-container | jq .

# 查看状态
docker inspect -f '{{.State.Status}}' my-container
podman inspect -f '{{.State.Status}}' my-container

# 查看重启次数
docker inspect -f '{{.RestartCount}}' my-container

# 查看环境变量
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' my-container
podman inspect -f '{{range .Config.Env}}{{println .}}{{end}}' my-container

# 查看日志路径 (Docker)
docker inspect -f '{{.LogPath}}' my-container

7.5 复制文件 (cp)

# 宿主机 → 容器
docker cp ./config.yml my-container:/app/config.yml
podman cp ./config.yml my-container:/app/config.yml

# 容器 → 宿主机
docker cp my-container:/app/logs/error.log ./error.log
podman cp my-container:/app/logs/error.log ./error.log

# 复制目录
docker cp ./configs/ my-container:/app/configs/
podman cp ./configs/ my-container:/app/configs/

# 归档模式(保留符号链接)
docker cp -a my-container:/app/data ./backup/
podman cp -a my-container:/app/data ./backup/

7.6 进入容器 (Exec)

这是在运行中的容器内部执行命令的核心调试工具。

# 进入交互式 shell
docker exec -it my-container bash
docker exec -it my-container sh      # 没有 bash 时(Alpine 等)
podman exec -it my-container bash

# 执行单条命令
docker exec my-container ls -la /app
podman exec my-container ls -la /app

# 设置环境变量后执行
docker exec -e DEBUG=true my-container node script.js
podman exec -e DEBUG=true my-container node script.js

# 以特定用户执行
docker exec -u root my-container apt-get update
podman exec -u root my-container dnf update

# 指定工作目录
docker exec -w /app my-container npm test
podman exec -w /app my-container npm test

# 在后台执行命令 (detach)
docker exec -d my-container touch /tmp/healthcheck
podman exec -d my-container touch /tmp/healthcheck

7.7 容器变更内容 (Diff)

# 查看容器文件系统的变更内容
docker diff my-container
podman diff my-container

# 输出示例:
# A /tmp/new-file          (Added)
# C /etc/nginx/nginx.conf  (Changed)
# D /var/log/old.log       (Deleted)

7.8 查看端口映射 (Port)

# 查看容器的端口映射
docker port my-container
podman port my-container

# 查看特定端口
docker port my-container 80
podman port my-container 80

# 输出示例:
# 80/tcp -> 0.0.0.0:8080
# 443/tcp -> 0.0.0.0:8443

7.9 事件监控 (Events)

# 实时事件流
docker events
podman events

# 按特定事件类型过滤
docker events --filter event=start
docker events --filter event=stop
docker events --filter event=die
podman events --filter event=start

# 只过滤特定容器的事件
docker events --filter container=my-container
podman events --filter container=my-container

# 指定时间范围
docker events --since 1h --until 30m
podman events --since 1h

# JSON 格式输出
docker events --format '{{json .}}'
podman events --format json

8. 网络管理

8.1 网络类型

网络驱动说明DockerPodman
bridge默认网络,通过虚拟网桥实现容器间通信docker0(默认网桥)Netavark (v4+) / CNI
host直接使用宿主机网络,无需端口映射支持支持
none无网络,完全隔离支持支持
macvlan给容器分配 MAC 地址,直连物理网络支持支持
overlay多宿主机网络 (Swarm)支持不支持
ipvlan与 macvlan 类似但共享同一 MAC支持支持

8.2 网络 CRUD 命令

# ============================================
# 创建网络 (Create)
# ============================================
# 创建默认 bridge 网络
docker network create my-network
podman network create my-network

# 指定子网与网关
docker network create \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  my-network
podman network create \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  my-network

# 指定 IP 范围
docker network create \
  --subnet 172.20.0.0/16 \
  --ip-range 172.20.240.0/20 \
  --gateway 172.20.0.1 \
  my-network

# 内部网络(无法从外部访问)
docker network create --internal internal-net
podman network create --internal internal-net

# 指定特定驱动
docker network create --driver macvlan \
  --subnet 192.168.1.0/24 \
  --gateway 192.168.1.1 \
  -o parent=eth0 \
  macvlan-net

# ============================================
# 网络列表 (List)
# ============================================
docker network ls
podman network ls

# ============================================
# 网络详细信息 (Inspect)
# ============================================
docker network inspect my-network
podman network inspect my-network

# 查看已连接的容器
docker network inspect -f '{{range .Containers}}{{.Name}} {{end}}' my-network

# ============================================
# 删除网络 (Remove)
# ============================================
docker network rm my-network
podman network rm my-network

# 清理未使用的网络
docker network prune -f
podman network prune -f

8.3 网络连接/断开

# 把运行中的容器连接到网络
docker network connect my-network my-container
podman network connect my-network my-container

# 以固定 IP 连接
docker network connect --ip 172.20.0.10 my-network my-container
podman network connect --ip 172.20.0.10 my-network my-container

# 从网络中断开
docker network disconnect my-network my-container
podman network disconnect my-network my-container

8.4 容器间通信示例

在用户自定义网络中,按容器名进行 DNS 解析 是自动完成的。

# 创建用户自定义网络
docker network create app-net
podman network create app-net

# 数据库容器
docker run -d --name db --network app-net \
  -e POSTGRES_PASSWORD=secret \
  postgres:16-alpine

# 应用容器(可以用名字访问 DB)
docker run -d --name app --network app-net \
  -e DATABASE_URL="postgresql://postgres:secret@db:5432/mydb" \
  -p 3000:3000 \
  myapp:latest

# 测试:从 app 容器 ping db
docker exec app ping -c 3 db
# PING db (172.20.0.2): 56 data bytes
# 64 bytes from 172.20.0.2: icmp_seq=0 ttl=64 time=0.123 ms

注意:在 Docker/Podman 的默认 bridge 网络中,无法按容器名进行 DNS 解析。必须创建 用户自定义网络(user-defined network),才能用容器名互相通信。

8.5 Docker: docker0 bridge vs Podman: Netavark/CNI

Docker 默认会创建一个名为 docker0 的 Linux bridge 接口,并用 iptables 规则来管理网络。

Podman 从 v4.0 起把默认网络栈从 CNI(Container Network Interface) 切换到了 Netavark。Netavark 是用 Rust 编写的容器网络栈,与 Aardvark-dns 一起提供 DNS 解析。

# 确认 Podman 网络后端
podman info --format '{{.Host.NetworkBackend}}'
# 输出: netavark

# Netavark + Aardvark-dns 结构:
# ┌──────────────┐     ┌──────────────┐
# │  Container A │     │  Container B │
# │  172.20.0.2  │     │  172.20.0.3  │
# └──────┬───────┘     └──────┬───────┘
#        │                     │
# ┌──────▼─────────────────────▼──────┐
# │        Netavark Bridge            │
# │   (基于 nftables 的网络管理)       │
# ├───────────────────────────────────┤
# │        Aardvark-dns               │
# │   (容器名 → IP 解析)              │
# └───────────────────────────────────┘

9. 卷与存储管理

9.1 Named Volume 管理

# ============================================
# 创建卷
# ============================================
docker volume create my-data
podman volume create my-data

# 指定驱动与选项
docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/share \
  nfs-volume

# 添加标签
docker volume create --label project=myapp --label env=prod my-data
podman volume create --label project=myapp --label env=prod my-data

# ============================================
# 卷列表
# ============================================
docker volume ls
podman volume ls

# 过滤
docker volume ls -f label=project=myapp
podman volume ls -f label=project=myapp

# Dangling 卷(未连接到任何容器)
docker volume ls -f dangling=true
podman volume ls -f dangling=true

# ============================================
# 卷详细信息
# ============================================
docker volume inspect my-data
podman volume inspect my-data

# 查看挂载点
docker volume inspect -f '{{.Mountpoint}}' my-data
podman volume inspect -f '{{.Mountpoint}}' my-data

# ============================================
# 删除卷
# ============================================
docker volume rm my-data
podman volume rm my-data

# 清理未使用的卷
docker volume prune -f
podman volume prune -f

9.2 Bind Mount vs Named Volume vs tmpfs

对比项Bind MountNamed Volumetmpfs
宿主机路径直接指定由 Docker/Podman 管理无(内存)
数据持久性永久保存在宿主机永久保存在卷中容器退出时删除
性能依赖宿主机 FS可优化最高(基于内存)
可移植性低(依赖宿主机路径)
备份自行管理docker volume 命令不可
使用场景挂载源码、配置文件DB 数据、上传文件临时文件、密钥
# Bind Mount
docker run -d -v /home/user/data:/app/data nginx
docker run -d --mount type=bind,source=/home/user/data,target=/app/data nginx

# Named Volume
docker run -d -v app-data:/app/data nginx
docker run -d --mount type=volume,source=app-data,target=/app/data nginx

# tmpfs(基于内存,容器退出时删除)
docker run -d --tmpfs /tmp:rw,size=100m nginx
docker run -d --mount type=tmpfs,destination=/tmp,tmpfs-size=100m nginx

# 只读挂载
docker run -d -v /host/config:/app/config:ro nginx
docker run -d --mount type=bind,source=/host/config,target=/app/config,readonly nginx

# 在 Podman 中设置 SELinux 标签
podman run -d -v /host/data:/app/data:Z nginx    # Z: 私有标签
podman run -d -v /host/data:/app/data:z nginx    # z: 共享标签

9.3 数据备份/恢复模式

# ============================================
# 备份卷数据
# ============================================
# 方法 1: 利用临时容器备份
docker run --rm \
  -v my-data:/source:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/my-data-backup.tar.gz -C /source .

podman run --rm \
  -v my-data:/source:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/my-data-backup.tar.gz -C /source .

# 方法 2: 按日期备份
docker run --rm \
  -v postgres-data:/source:ro \
  -v $(pwd)/backups:/backup \
  alpine tar czf /backup/postgres-$(date +%Y%m%d).tar.gz -C /source .

# ============================================
# 恢复卷数据
# ============================================
# 创建新卷后恢复
docker volume create restored-data

docker run --rm \
  -v restored-data:/target \
  -v $(pwd):/backup:ro \
  alpine tar xzf /backup/my-data-backup.tar.gz -C /target

podman run --rm \
  -v restored-data:/target \
  -v $(pwd):/backup:ro \
  alpine tar xzf /backup/my-data-backup.tar.gz -C /target

# ============================================
# 卷之间的数据迁移
# ============================================
docker run --rm \
  -v old-volume:/from:ro \
  -v new-volume:/to \
  alpine sh -c "cp -a /from/. /to/"

10. Docker Compose vs Podman Compose

10.1 docker-compose.yml 基本结构

# docker-compose.yml(或 compose.yml)
version: '3.9' # Compose 文件格式版本(v2 中为可选)

services:
  web:
    build: ./app # Dockerfile 路径
    image: myapp:latest # 构建出的镜像名
    container_name: myapp-web # 容器名
    ports:
      - '3000:3000' # 端口映射
    environment: # 环境变量
      - NODE_ENV=production
      - DB_HOST=db
    env_file: # 环境变量文件
      - .env
    volumes: # 挂载卷
      - ./app:/app
      - node_modules:/app/node_modules
    depends_on: # 依赖关系
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks: # 网络
      - app-net
    restart: unless-stopped # 重启策略
    deploy: # 资源限制
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
    healthcheck: # 健康检查
      test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    image: postgres:16-alpine
    container_name: myapp-db
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-net
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U admin -d myapp']
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: myapp-redis
    command: redis-server --appendonly yes --maxmemory 256mb
    volumes:
      - redis-data:/data
    networks:
      - app-net

volumes:
  postgres-data:
    driver: local
  redis-data:
    driver: local
  node_modules:

networks:
  app-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16

10.2 主要 Compose 命令

# ============================================
# Docker Compose (v2: docker compose / v1: docker-compose)
# ============================================

# 启动服务(后台)
docker compose up -d
docker compose -f docker-compose.prod.yml up -d

# 构建后启动服务
docker compose up -d --build

# 只启动特定服务
docker compose up -d web db

# 扩缩容(调整服务实例数)
docker compose up -d --scale web=3

# 停止服务并清理资源
docker compose down

# 连同卷一起删除
docker compose down -v

# 连同镜像一起删除
docker compose down --rmi all

# 服务列表与状态
docker compose ps

# 服务日志
docker compose logs
docker compose logs -f web
docker compose logs --tail 100 web db

# 在服务内部执行命令
docker compose exec web bash
docker compose exec db psql -U admin -d myapp

# 执行一次性命令(run 会创建新容器)
docker compose run --rm web npm test
docker compose run --rm web python manage.py migrate

# 构建服务
docker compose build
docker compose build --no-cache web

# 重启服务
docker compose restart
docker compose restart web

# 校验配置有效性
docker compose config

# 拉取镜像
docker compose pull

# ============================================
# Podman Compose
# ============================================

# 安装 podman-compose
pip3 install podman-compose

# 或者在 Podman 4.7+ 中使用 podman compose(插件方式)
# 用法与 docker compose 相同

podman compose up -d
podman compose down
podman compose ps
podman compose logs -f web
podman compose exec web bash

10.3 实战示例:Web 应用 + DB + Redis 三层架构

# compose.yml —— 生产环境三层架构
services:
  # ============================================
  # Tier 1: Reverse Proxy (Nginx)
  # ============================================
  nginx:
    image: nginx:1.25-alpine
    container_name: proxy
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      app:
        condition: service_healthy
    networks:
      - frontend
    restart: unless-stopped

  # ============================================
  # Tier 2: Application (Node.js)
  # ============================================
  app:
    build:
      context: ./app
      dockerfile: Dockerfile
      args:
        NODE_ENV: production
    container_name: app
    expose:
      - '3000'
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_NAME=appdb
      - DB_USER=appuser
      - DB_PASS=${DB_PASSWORD}
      - REDIS_URL=redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - frontend
      - backend
    healthcheck:
      test:
        [
          'CMD',
          'node',
          '-e',
          "require('http').get('http://localhost:3000/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1) })",
        ]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G
    restart: unless-stopped

  # ============================================
  # Tier 3: Database (PostgreSQL)
  # ============================================
  postgres:
    image: postgres:16-alpine
    container_name: postgres
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./db/init:/docker-entrypoint-initdb.d:ro
    networks:
      - backend
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U appuser -d appdb']
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
    restart: unless-stopped

  # ============================================
  # Cache: Redis
  # ============================================
  redis:
    image: redis:7-alpine
    container_name: redis
    command: >
      redis-server
      --appendonly yes
      --maxmemory 256mb
      --maxmemory-policy allkeys-lru
      --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
    networks:
      - backend
    healthcheck:
      test: ['CMD', 'redis-cli', '-a', '${REDIS_PASSWORD}', 'ping']
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
    restart: unless-stopped

volumes:
  postgres-data:
    driver: local
  redis-data:
    driver: local

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true # 阻断外部访问

10.4 按环境划分配置 (Override)

# compose.override.yml —— 开发环境(自动加载)
services:
  app:
    build:
      args:
        NODE_ENV: development
    volumes:
      - ./app/src:/app/src # 源码热重载
    environment:
      - NODE_ENV=development
      - DEBUG=app:*
    ports:
      - '3000:3000' # 开发时直接访问
      - '9229:9229' # Node.js 调试器端口

  postgres:
    ports:
      - '5432:5432' # 开发时直接访问
# compose.prod.yml —— 生产环境
services:
  app:
    build:
      args:
        NODE_ENV: production
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '4.0'
          memory: 2G

  nginx:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 256M
# 开发环境(自动合并 compose.yml + compose.override.yml)
docker compose up -d

# 生产环境(排除 override,使用 prod 配置)
docker compose -f compose.yml -f compose.prod.yml up -d

# 使用按环境划分的 .env 文件
docker compose --env-file .env.production up -d

11. Podman 独有功能

11.1 Pod 管理

Podman 的 Pod 与 Kubernetes 的 Pod 是同一个概念。同一个 Pod 内的容器会共享 Network namespace、IPC namespace、PID namespace

# ============================================
# 创建 Pod
# ============================================
# 创建基本 Pod
podman pod create --name my-pod

# 创建时带端口映射(在 Pod 层面定义端口)
podman pod create --name web-pod -p 8080:80 -p 8443:443

# 指定网络
podman pod create --name web-pod --network my-network

# ============================================
# 向 Pod 添加容器
# ============================================
# 在 Pod 中运行容器(--pod 选项)
podman run -d --pod my-pod --name nginx nginx:latest
podman run -d --pod my-pod --name php php:8.2-fpm

# Pod 内的容器之间可以通过 localhost 通信
# nginx → localhost:9000 → php-fpm

# ============================================
# Pod 管理
# ============================================
# Pod 列表
podman pod list
podman pod ps

# Pod 详细信息
podman pod inspect my-pod

# 启动/停止/重启 Pod
podman pod start my-pod
podman pod stop my-pod
podman pod restart my-pod

# 暂停/恢复 Pod
podman pod pause my-pod
podman pod unpause my-pod

# 删除 Pod(含容器)
podman pod rm my-pod
podman pod rm -f my-pod    # 强制删除

# 删除所有 Pod
podman pod rm -a -f

# 查看 Pod 的进程
podman pod top my-pod

# Pod 的资源使用量
podman pod stats my-pod

11.2 生成 Kubernetes YAML (podman generate kube)

可以把本地测试过的容器/Pod 配置自动转换成 Kubernetes YAML。

# 从容器生成 K8s YAML
podman generate kube my-container > deployment.yaml

# 从 Pod 生成 K8s YAML
podman generate kube my-pod > pod.yaml

# 包含 Service 信息
podman generate kube --service my-pod > pod-with-service.yaml

# 生成的 YAML 示例:
# apiVersion: v1
# kind: Pod
# metadata:
#   name: my-pod
# spec:
#   containers:
#   - name: nginx
#     image: nginx:latest
#     ports:
#     - containerPort: 80
#       hostPort: 8080
#   - name: php
#     image: php:8.2-fpm

11.3 运行 Kubernetes YAML (podman play kube)

反过来,也可以在 Podman 中直接运行 Kubernetes YAML 文件。

# 用 K8s YAML 文件创建 Pod
podman play kube deployment.yaml

# 支持 ConfigMap
podman play kube pod.yaml --configmap configmap.yaml

# 支持密钥
podman play kube pod.yaml --seccomp-profile-root ./profiles

# 替换已有资源(更新)
podman play kube --replace pod.yaml

# 删除资源
podman play kube --down pod.yaml

# 包含构建
podman play kube --build pod.yaml

11.4 生成 Systemd 服务

# 把容器生成为 systemd 服务
podman generate systemd --name my-container > ~/.config/systemd/user/my-container.service

# 应用新配置(用户级别)
systemctl --user daemon-reload
systemctl --user enable my-container.service
systemctl --user start my-container.service

# 把 Pod 生成为 systemd 服务
podman generate systemd --name my-pod --files
# → 生成 pod-my-pod.service, container-nginx.service, container-php.service

# 选项
podman generate systemd --name my-container \
  --restart-policy always \
  --time 30 \
  --new    # 启动时创建新容器(推荐)

11.5 Quadlet:systemd 原生容器管理

Quadlet 是 Podman 4.4+ 引入的机制,用 systemd unit 文件格式以声明式方式管理容器。

# ~/.config/containers/systemd/webapp.container
[Unit]
Description=My Web Application
After=network-online.target

[Container]
Image=docker.io/library/nginx:latest
ContainerName=webapp
PublishPort=8080:80
Volume=webapp-data:/usr/share/nginx/html:ro
Environment=NGINX_WORKER_PROCESSES=auto
AutoUpdate=registry
HealthCmd=curl -f http://localhost/ || exit 1
HealthInterval=30s

[Service]
Restart=always
TimeoutStartSec=300

[Install]
WantedBy=default.target
# 放置 Quadlet 文件后应用
# 系统级别: /etc/containers/systemd/
# 用户级别: ~/.config/containers/systemd/

systemctl --user daemon-reload
systemctl --user start webapp.service
systemctl --user enable webapp.service
systemctl --user status webapp.service

# 查看日志
journalctl --user -u webapp.service -f

11.6 Rootless Containers 详解

Podman 的 rootless 容器利用 User Namespace,把容器内部的 root(UID 0)映射到宿主机上的普通用户 UID。

# 查看当前用户的 UID 映射
cat /etc/subuid
# user1:100000:65536
# → user1 把宿主机 UID 100000~165535 用作容器内部 UID

cat /etc/subgid
# user1:100000:65536

# 映射结构:
# 容器 UID 0 (root) → 宿主机 UID 100000(普通用户)
# 容器 UID 1        → 宿主机 UID 100001
# 容器 UID 65535    → 宿主机 UID 165535

# 确认 rootless 状态
podman info --format '{{.Host.Security.Rootless}}'
# true

# 确认 rootless 模式下的限制事项
podman info | grep -A 5 rootless

# 运行 rootless 容器(默认值)
podman run -d --name rootless-nginx -p 8080:80 nginx

# 在宿主机上查看进程 —— 以普通用户而非 root 运行
ps aux | grep nginx
# user1    12345  ... nginx: master process

12. 安全相关命令与配置

12.1 Rootless 模式配置

# ============================================
# Docker Rootless 模式配置
# ============================================
# 安装 rootless Docker
curl -fsSL https://get.docker.com/rootless | sh

# 设置环境变量 (~/.bashrc)
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

# 启动 rootless Docker
systemctl --user start docker
systemctl --user enable docker

# ============================================
# Podman Rootless 模式配置(默认启用)
# ============================================
# 确认 subuid/subgid 配置
grep $USER /etc/subuid /etc/subgid

# 若没有配置则添加
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER

# 命名空间迁移(重新映射已有镜像)
podman system migrate

12.2 Security Options

# ============================================
# Capability 管理
# ============================================
# 移除全部 capability 后只添加需要的(最小权限原则)
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
podman run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx

# 确认默认 capability
docker run --rm alpine cat /proc/1/status | grep Cap
podman run --rm alpine cat /proc/1/status | grep Cap

# 主要 Capability 列表:
# NET_BIND_SERVICE : 绑定 1024 以下端口
# SYS_PTRACE       : 进程调试 (strace 等)
# NET_RAW           : 使用 RAW 套接字 (ping 等)
# CHOWN             : 修改文件所有权
# DAC_OVERRIDE      : 忽略文件权限
# SETUID/SETGID     : 修改 UID/GID

# ============================================
# Security Options
# ============================================
# no-new-privileges: 防止 execve 时提权
docker run --security-opt no-new-privileges:true myapp
podman run --security-opt no-new-privileges:true myapp

# 应用 Seccomp 配置文件
docker run --security-opt seccomp=./custom-seccomp.json myapp
podman run --security-opt seccomp=./custom-seccomp.json myapp

# AppArmor 配置文件 (Docker/Ubuntu)
docker run --security-opt apparmor=docker-default myapp

# SELinux 标签 (Podman/RHEL)
podman run --security-opt label=type:container_t myapp
podman run --security-opt label=disable myapp    # 关闭 SELinux

12.3 只读容器

# 把文件系统设为只读
docker run --read-only nginx
podman run --read-only nginx

# 只把需要写入的目录挂载为 tmpfs
docker run --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  --tmpfs /var/cache/nginx \
  nginx

podman run --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  --tmpfs /var/cache/nginx \
  nginx

12.4 镜像安全验证

# ============================================
# Docker Content Trust (DCT)
# ============================================
# 只允许拉取/运行已签名的镜像
export DOCKER_CONTENT_TRUST=1
docker pull nginx:latest    # 已验证签名

# 给镜像签名
docker trust sign myregistry.com/myapp:v1.0

# 确认签名
docker trust inspect --pretty myregistry.com/myapp:v1.0

# ============================================
# Podman 镜像签名(基于 GPG)
# ============================================
# 查看签名策略文件
cat /etc/containers/policy.json

# 用 GPG 密钥给镜像签名
podman push --sign-by security@example.com myregistry.com/myapp:v1.0

# 签名策略配置示例 (/etc/containers/policy.json)
# {
#   "default": [{"type": "reject"}],
#   "transports": {
#     "docker": {
#       "myregistry.com": [
#         {
#           "type": "signedBy",
#           "keyType": "GPGKeys",
#           "keyPath": "/etc/pki/rpm-gpg/RPM-GPG-KEY-myorg"
#         }
#       ],
#       "docker.io": [{"type": "insecureAcceptAnything"}]
#     }
#   }
# }

# 用 Skopeo 验证镜像完整性
skopeo inspect --raw docker://myregistry.com/myapp:v1.0 | jq .

12.5 安全最佳实践检查清单

项目Docker 命令 / 配置Podman 命令 / 配置
禁止以 root 运行USER nonroot in Dockerfile默认 rootless
最小化 Capability--cap-drop ALL --cap-add ...--cap-drop ALL --cap-add ...
Read-only FS--read-only--read-only
防止提权--security-opt no-new-privileges--security-opt no-new-privileges
资源限制--memory --cpus --pids-limit--memory --cpus --pids-limit
网络隔离--network none / internal network--network none / internal network
镜像签名Docker Content TrustGPG signing / sigstore
密钥管理Docker Secrets / 环境变量Podman secrets / 环境变量
基础镜像distroless / scratch / alpinedistroless / scratch / alpine
镜像扫描docker scout / TrivyTrivy / Grype

13. 系统管理与清理

13.1 查看磁盘占用 (system df)

# 磁盘占用摘要
docker system df
podman system df

# 详细信息
docker system df -v
podman system df -v

# 输出示例:
# TYPE            TOTAL    ACTIVE   SIZE      RECLAIMABLE
# Images          15       5        4.2GB     2.8GB (66%)
# Containers      8        3        120MB     80MB (66%)
# Local Volumes   10       4        1.5GB     800MB (53%)
# Build Cache     20       0        500MB     500MB (100%)

13.2 整体清理 (system prune)

# 清理已停止的容器 + dangling 镜像 + 未使用的网络 + 构建缓存
docker system prune
podman system prune

# 不显示确认提示直接执行
docker system prune -f
podman system prune -f

# 连未使用的镜像一起清理(注意!)
docker system prune -a -f
podman system prune -a -f

# 连卷一起清理(非常注意!可能丢失数据)
docker system prune -a --volumes -f
podman system prune -a --volumes -f

# 只清理超过特定时间的资源
docker system prune -a --filter "until=720h" -f    # 30 天以上

13.3 系统信息与版本

# 系统整体信息
docker info
podman info

# 主要确认项:
# - Storage Driver
# - Cgroup Version (v1/v2)
# - Security Options
# - Kernel Version
# - OS/Architecture
# - Registry 配置

# 版本信息
docker version
podman version

# 分别确认客户端/服务端版本
docker version --format '{{.Client.Version}}'
docker version --format '{{.Server.Version}}'
podman version --format '{{.Client.Version}}'

14. 实战速查表:最常用的 30 条命令

下面这张表把容器运维中使用频率最高的 30 条命令一览式整理出来。Docker 和 Podman 使用完全相同的语法。

#操作命令
1下载镜像docker pull nginx:latest
2镜像列表docker images
3删除镜像docker rmi nginx:latest
4清理 Dangling 镜像docker image prune -f
5构建镜像docker build -t myapp:latest .
6运行容器(后台)docker run -d --name web -p 80:80 nginx
7运行容器(交互式)docker run -it --rm alpine sh
8运行中的容器列表docker ps
9所有容器列表docker ps -a
10停止容器docker stop web
11启动容器docker start web
12重启容器docker restart web
13删除容器docker rm web
14强制删除容器docker rm -f web
15删除所有已停止容器docker container prune -f
16容器日志docker logs -f --tail 100 web
17进入容器 (exec)docker exec -it web bash
18复制文件(宿主机→容器)docker cp file.txt web:/app/
19复制文件(容器→宿主机)docker cp web:/app/log.txt ./
20资源监控docker stats
21详细信息 (JSON)docker inspect web
22创建网络docker network create my-net
23网络列表docker network ls
24创建卷docker volume create my-vol
25卷列表docker volume ls
26启动 Compose 服务docker compose up -d
27停止 Compose 服务docker compose down
28登录registrydocker login registry.example.com
29推送镜像docker push myregistry.com/app:v1
30清理整个系统docker system prune -a -f

Tip:上面所有命令把 docker 换成 podman 都能照常运行。


15. 故障排查指南

15.1 权限相关问题 (Permission Issues)

Docker: "permission denied while trying to connect to the Docker daemon socket"

# 原因: 当前用户不属于 docker 组
# 解决:
sudo usermod -aG docker $USER
newgrp docker    # 或者重新登录

# 确认
groups $USER
docker ps        # 应当无错误运行

Podman Rootless: "Error: could not get runtime: cannot re-exec process"

# 原因: 未配置 subuid/subgid
# 解决:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER

# 命名空间迁移
podman system migrate

# 确认
podman unshare cat /proc/self/uid_map

Podman Rootless: 卷挂载权限问题

# 原因: 宿主机与容器的 UID 映射不一致
# 方法 1: 用 unshare 修改所有权
podman unshare chown 1000:1000 /host/path/data

# 方法 2: 用 :U 选项自动映射 UID (Podman 4.0+)
podman run -v /host/data:/data:U myapp

# 方法 3: 添加 SELinux 标签 (RHEL/CentOS)
podman run -v /host/data:/data:Z myapp

15.2 网络连接问题

容器无法访问外部网络

# 确认 DNS
docker exec my-container cat /etc/resolv.conf
docker exec my-container nslookup google.com

# 解决 1: 直接指定 DNS 服务器
docker run --dns 8.8.8.8 --dns 8.8.4.4 myapp

# 解决 2: 检查 iptables/nftables 规则
sudo iptables -L -n -t nat
sudo nft list ruleset

# 解决 3: 启用 IP 转发
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
# 永久配置: 在 /etc/sysctl.conf 中添加 net.ipv4.ip_forward=1

容器之间无法用名字通信

# 原因: 正在使用默认 bridge 网络(不支持 DNS 解析)
# 解决: 创建用户自定义网络
docker network create my-net
docker run -d --network my-net --name db postgres:16
docker run -d --network my-net --name app \
  -e DB_HOST=db myapp    # 现在可以用 "db" 访问了

Podman rootless 绑定 1024 以下端口失败

# 原因: rootless 模式下默认无法使用 1024 以下端口
# 解决 1: 使用 1024 以上端口
podman run -d -p 8080:80 nginx

# 解决 2: 用 sysctl 修改 unprivileged 端口范围
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
# 永久配置: /etc/sysctl.conf

# 解决 3: 使用 rootful 模式
sudo podman run -d -p 80:80 nginx

15.3 存储/磁盘空间问题

"no space left on device" 错误

# 确认磁盘占用
docker system df -v
podman system df -v

# 分阶段清理:
# 第 1 步: 删除已停止的容器
docker container prune -f

# 第 2 步: 删除 Dangling 镜像
docker image prune -f

# 第 3 步: 删除未使用的卷(注意: 先确认数据!)
docker volume prune -f

# 第 4 步: 删除构建缓存
docker builder prune -f

# 第 5 步: 整体清理(最后手段)
docker system prune -a --volumes -f

# 只清理超过特定时间的镜像
docker image prune -a --filter "until=720h" -f    # 30 天以上

修改 Podman 存储路径

# Rootless Podman 的默认存储位置
# ~/.local/share/containers/storage/

# 修改存储路径: ~/.config/containers/storage.conf
# [storage]
# driver = "overlay"
# graphroot = "/mnt/large-disk/containers/storage"

# 修改后迁移
podman system reset    # 注意: 会删除所有容器/镜像

15.4 镜像构建失败

Multi-platform 构建错误 (Docker)

# 需要安装 QEMU 模拟器
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

# 创建 buildx 构建器
docker buildx create --name multiarch --use
docker buildx inspect multiarch --bootstrap

# 多平台构建
docker buildx build --platform linux/amd64,linux/arm64 \
  -t myapp:latest --push .

构建上下文过大的情况

# 创建 .dockerignore 文件
cat > .dockerignore << 'EOF'
.git
node_modules
dist
*.log
.env
.DS_Store
**/*.test.js
**/*.spec.js
coverage
.nyc_output
EOF

# 确认构建上下文大小
du -sh . --exclude=.git --exclude=node_modules

# 只把特定路径用作构建上下文
docker build -f Dockerfile -t myapp . --build-context src=./src

15.5 通用调试模式

# ============================================
# 调试立即退出的容器
# ============================================
# 查看退出日志
docker logs my-container
docker inspect -f '{{.State.ExitCode}}' my-container
docker inspect -f '{{.State.Error}}' my-container

# 覆盖 entrypoint 进入 shell
docker run -it --entrypoint sh myapp:latest
podman run -it --entrypoint sh myapp:latest

# ============================================
# 网络调试
# ============================================
# 网络调试专用容器
docker run --rm -it --network container:target-container \
  nicolaka/netshoot bash

# 在特定网络中调试
docker run --rm -it --network my-net nicolaka/netshoot bash
# → 可以使用 nslookup, dig, curl, tcpdump, iperf3, netstat 等

# ============================================
# 文件系统调试
# ============================================
# 确认容器文件系统的变更内容
docker diff my-container

# 把容器的文件系统提取为 tar 后分析
docker export my-container | tar -tf - | head -50

# ============================================
# 确认资源限制
# ============================================
# 确认容器的 cgroup 配置
docker exec my-container cat /sys/fs/cgroup/memory.max
docker exec my-container cat /sys/fs/cgroup/cpu.max

# 确认是否被 OOM Killed
docker inspect -f '{{.State.OOMKilled}}' my-container

16. 参考资料与文献

官方文档

OCI 标准

安全指南

相关项目

현재 단락 (1/1215)

2013 年 Solomon Hykes 在 PyCon 上用一场 5 分钟的闪电演讲「The future of Linux Containers」把 Docker 公之于众。它带动了容器技术的普及...

작성 글자: 0원문 글자: 53,195작성 단락: 0/1215