LabHub

博客

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

한국어English日本語中文


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 三款工具来解决这些问题。

这三款工具各自独立工作,同时又互为补充。这是一种忠实遵循 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 标准

安全指南

相关项目

评论

还没有评论。

登录后即可发表评论