强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

13 - Docker 迁移

第 13 章 — Docker 迁移

13.1 迁移概述

Podman 的 CLI 与 Docker 高度兼容,大多数情况下可以直接替换。但仍有一些差异需要注意。

迁移难度评估

场景迁移难度说明
基本容器操作⭐ 极简alias docker=podman 即可
Dockerfile 构建⭐ 极简直接兼容
端口映射/卷挂载⭐ 极简直接兼容
Docker Compose⭐⭐ 简单使用 podman-compose
CI/CD 流水线⭐⭐ 简单替换命令即可
Docker Socket API⭐⭐⭐ 中等需要 podman.socket 兼容层
Docker Swarm⭐⭐⭐⭐ 困难需要迁移到 K8s 或 Quadlet
Docker Desktop 特性⭐⭐⭐ 中等使用 Podman Desktop

13.2 快速迁移(alias)

# 最简单的迁移方式 — 设置别名
alias docker=podman
alias docker-compose=podman-compose

# 永久生效
echo 'alias docker=podman' >> ~/.bashrc
echo 'alias docker-compose=podman-compose' >> ~/.bashrc
source ~/.bashrc

# 验证
docker run --rm alpine echo "Hello from Docker... I mean Podman!"

💡 提示

这种方式适合个人开发环境。生产环境建议直接使用 podman 命令,避免混淆。


13.3 命令映射表

13.3.1 容器操作

Docker 命令Podman 命令说明
docker runpodman run✅ 完全兼容
docker pspodman ps✅ 完全兼容
docker execpodman exec✅ 完全兼容
docker stoppodman stop✅ 完全兼容
docker rmpodman rm✅ 完全兼容
docker logspodman logs✅ 完全兼容
docker inspectpodman inspect✅ 完全兼容
docker cppodman cp✅ 完全兼容
docker toppodman top✅ 完全兼容
docker statspodman stats✅ 完全兼容
docker diffpodman diff✅ 完全兼容
docker renamepodman rename✅ 完全兼容
docker waitpodman wait✅ 完全兼容
docker attachpodman attach✅ 完全兼容
docker commitpodman commit✅ 完全兼容
docker killpodman kill✅ 完全兼容
docker pausepodman pause✅ 完全兼容
docker unpausepodman unpause✅ 完全兼容

13.3.2 镜像操作

Docker 命令Podman 命令说明
docker pullpodman pull✅ 完全兼容
docker pushpodman push✅ 完全兼容
docker buildpodman build✅ 兼容(内部调用 Buildah)
docker tagpodman tag✅ 完全兼容
docker imagespodman images✅ 完全兼容
docker rmipodman rmi✅ 完全兼容
docker savepodman save✅ 完全兼容
docker loadpodman load✅ 完全兼容
docker historypodman history✅ 完全兼容
docker importpodman import✅ 完全兼容
docker exportpodman export✅ 完全兼容

13.3.3 系统操作

Docker 命令Podman 命令差异
docker system dfpodman system df✅ 兼容
docker system prunepodman system prune✅ 兼容
docker infopodman info格式不同
docker loginpodman login✅ 兼容
docker logoutpodman logout✅ 兼容
docker volume createpodman volume create✅ 兼容
docker network createpodman network create✅ 兼容
docker composepodman-compose需单独安装

13.3.4 有差异的命令

Docker 命令Podman 对应差异说明
docker swarmPodman 不支持 Swarm
docker stack使用 Quadlet 或 K8s 替代
docker service使用 systemd 或 K8s 替代
docker contextPodman 使用 SSH 远程连接
docker buildxpodman build --platform多架构构建方式不同
docker compose uppodman kube play或使用 podman-compose

13.4 Docker Socket 兼容层

许多工具(如 Portainer、Watchtower)依赖 Docker Socket (/var/run/docker.sock)。Podman 提供兼容的 REST API。

13.4.1 启用 Podman Socket

# 用户级 Socket(Rootless)
systemctl --user enable --now podman.socket

# 验证 Socket
ls /run/user/$(id -u)/podman/podman.sock

# 设置 DOCKER_HOST 环境变量
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock

# 或创建软链接
ln -s /run/user/$(id -u)/podman/podman.sock /var/run/docker.sock
# ⚠️ Rootless 用户可能需要 sudo

# 测试 API
curl -s --unix-socket /run/user/$(id -u)/podman/podman.sock http://localhost/v1.41/info | python3 -m json.tool | head

13.4.2 系统级 Socket(Root)

# 启用系统级 podman.socket
sudo systemctl enable --now podman.socket

# 查看 Socket
ls /run/podman/podman.sock

# 创建 docker.sock 兼容
sudo ln -sf /run/podman/podman.sock /var/run/docker.sock

# 现在 Docker SDK 可以正常使用
export DOCKER_HOST=unix:///run/podman/podman.sock

13.4.3 使用 Docker SDK(Python 示例)

# 无需修改代码,直接使用 Docker SDK
import docker

# 连接到 Podman Socket
client = docker.DockerClient(base_url='unix:///run/user/1000/podman/podman.sock')

# 列出容器(与 Docker 完全一致的 API)
for container in client.containers.list():
    print(container.name, container.status)

# 运行容器
container = client.containers.run("alpine", "echo hello", detach=True)
print(container.logs().decode())

13.5 迁移 Docker Compose 项目

13.5.1 直接替换

# 大多数 docker-compose.yaml 可以直接使用

# 安装 podman-compose
pip3 install podman-compose

# 直接使用现有 compose 文件
cd /path/to/project
podman-compose up -d

# 检查状态
podman-compose ps
podman-compose logs

13.5.2 需要修改的内容

# docker-compose.yaml 修改清单

services:
  app:
    image: myapp:v1.0
    volumes:
      - ./data:/app/data:Z     # 1. 添加 :Z SELinux 标签
      # 如果不是 SELinux 系统,:Z 无副作用,建议保留
    ports:
      - "8080:80"              # 2. 避免 < 1024 的端口
    # 不要使用:               # 3. 移除不兼容配置
    #   container_name: xxx    #    (扩展时会冲突)
    #   network_mode: host     #    (Rootless 下行为不同)

  db:
    image: postgres:16
    tmpfs:
      - /tmp:size=100m         # 4. 添加 tmpfs 减少磁盘写入
    volumes:
      - pgdata:/var/lib/postgresql/data:Z

13.6 CI/CD 迁移

13.6.1 GitLab CI

# .gitlab-ci.yml
# Before(Docker):
#   image: docker:latest
#   services:
#     - docker:dind
#   script:
#     - docker build -t myapp .
#     - docker push registry.example.com/myapp

# After(Podman):
build:
  image: quay.io/podman/stable
  services:
    - name: registry:2
      alias: registry
  variables:
    DOCKER_HOST: unix:///run/podman/podman.sock
  script:
    - podman build -t myapp .
    - podman tag myapp registry/myapp:latest
    - podman push registry/myapp:latest

13.6.2 GitHub Actions

# .github/workflows/build.yml
name: Build and Push

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Podman
        run: |
          sudo apt-get update
          sudo apt-get install -y podman

      - name: Login to Registry
        run: |
          echo "${{ secrets.REGISTRY_PASSWORD }}" | \
          podman login registry.example.com -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin

      - name: Build Image
        run: podman build -t registry.example.com/myapp:${{ github.sha }} .

      - name: Push Image
        run: podman push registry.example.com/myapp:${{ github.sha }}

13.6.3 Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent {
        // 使用包含 Podman 的 Jenkins agent
        label 'podman-agent'
    }
    stages {
        stage('Build') {
            steps {
                sh 'podman build -t myapp:${BUILD_NUMBER} .'
            }
        }
        stage('Push') {
            steps {
                sh '''
                    echo "${REGISTRY_PASSWORD}" | podman login registry.example.com -u ${REGISTRY_USERNAME} --password-stdin
                    podman tag myapp:${BUILD_NUMBER} registry.example.com/myapp:${BUILD_NUMBER}
                    podman push registry.example.com/myapp:${BUILD_NUMBER}
                '''
            }
        }
    }
}

13.7 常见迁移陷阱

陷阱 1:Root 模式 vs Rootless 模式

# Docker 默认以 root 运行
sudo docker run -p 80:80 nginx   # ✅ 可以绑定特权端口

# Podman 默认以 Rootless 运行
podman run -p 80:80 nginx        # ❌ Rootless 无法绑定 80 端口
podman run -p 8080:80 nginx      # ✅ 使用非特权端口

解决:配置 sysctl 或使用反向代理(见第 05 章)。

陷阱 2:SELinux 标签

# Docker 中直接挂载目录
docker run -v /data:/data nginx  # ✅ 通常可以工作

# Podman(SELinux 系统)需要标签
podman run -v /data:/data:Z nginx  # ✅ 添加 :Z

解决:所有卷挂载添加 :Z(私有)或 :z(共享)。

陷阱 3:容器命名冲突

# Docker 容器停止后仍占用名称
docker run --name web nginx
docker stop web
docker run --name web nginx  # ❌ 名称冲突

# Podman 同样,需要先删除
podman rm web
podman run --name web nginx

陷阱 4:镜像仓库前缀

# Docker 默认添加 docker.io/library/ 前缀
docker pull alpine
# 等价于 docker.io/library/alpine:latest

# Podman 同样,但可能需要配置 registries.conf
podman pull alpine
# 等价于 docker.io/library/alpine:latest

陷阱 5:Dockerfile 中的 HEALTHCHECK

# Dockerfile 中的 HEALTHCHECK 指令
HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost/ || exit 1
# Podman 对 HEALTHCHECK 的支持需要手动触发
podman healthcheck run <container>
podman inspect --format '{{.State.Health.Status}}' <container>

陷阱 6:容器内 PID 1

# Docker 中 systemd 作为 PID 1(需要 --privileged)
docker run --privileged centos /usr/sbin/init

# Podman 中 systemd 可以直接运行(更友好)
podman run --systemd=always fedora /usr/sbin/init
# 或
podman run --systemd=always centos /usr/lib/systemd/systemd

13.8 迁移验证脚本

#!/bin/bash
# migration-test.sh — 验证从 Docker 到 Podman 的迁移

echo "=== Podman 迁移验证 ==="

# 1. 基本运行
echo -n "1. 基本容器运行: "
podman run --rm alpine echo "OK" > /dev/null 2>&1 && echo "✅" || echo "❌"

# 2. 端口映射
echo -n "2. 端口映射: "
podman run --rm -d --name test-port -p 18080:80 nginx:alpine > /dev/null 2>&1
sleep 2
curl -s http://localhost:18080 > /dev/null 2>&1 && echo "✅" || echo "❌"
podman rm -f test-port > /dev/null 2>&1

# 3. 卷挂载
echo -n "3. 卷挂载: "
mkdir -p /tmp/podman-test
echo "test" > /tmp/podman-test/file.txt
podman run --rm -v /tmp/podman-test:/data:Z alpine cat /data/file.txt | grep -q "test" && echo "✅" || echo "❌"
rm -rf /tmp/podman-test

# 4. 环境变量
echo -n "4. 环境变量: "
podman run --rm -e MY_VAR=hello alpine printenv MY_VAR | grep -q "hello" && echo "✅" || echo "❌"

# 5. 镜像构建
echo -n "5. 镜像构建: "
echo "FROM alpine" > /tmp/Dockerfile.test
echo "RUN echo built" >> /tmp/Dockerfile.test
podman build -t test-build -f /tmp/Dockerfile.test /tmp > /dev/null 2>&1 && echo "✅" || echo "❌"
podman rmi test-build > /dev/null 2>&1
rm /tmp/Dockerfile.test

# 6. Socket 兼容
echo -n "6. Socket 兼容: "
SOCKET=/run/user/$(id -u)/podman/podman.sock
[ -S "$SOCKET" ] && echo "✅" || echo "❌ (需要: systemctl --user enable podman.socket)"

# 7. 网络
echo -n "7. 自定义网络: "
podman network create test-net > /dev/null 2>&1
podman network inspect test-net > /dev/null 2>&1 && echo "✅" || echo "❌"
podman network rm test-net > /dev/null 2>&1

# 8. Secrets
echo -n "8. Secrets: "
echo "secret" | podman secret create test-secret - > /dev/null 2>&1
podman secret inspect test-secret > /dev/null 2>&1 && echo "✅" || echo "❌"
podman secret rm test-secret > /dev/null 2>&1

echo "=== 验证完成 ==="

13.9 迁移步骤总结

迁移检查清单:

□ 1. 安装 Podman
□ 2. 配置 Rootless(/etc/subuid, /etc/subgid)
□ 3. 配置 registries.conf(镜像仓库)
□ 4. 测试基本容器操作
□ 5. 验证 Dockerfile 构建
□ 6. 修改卷挂载(添加 :Z)
□ 7. 调整端口映射(避免 < 1024)
□ 8. 安装 podman-compose(如果使用 Docker Compose)
□ 9. 启用 podman.socket(如果依赖 Docker API)
□ 10. 更新 CI/CD 流水线
□ 11. 测试所有应用功能
□ 12. 切换生产环境
□ 13. 监控运行状态
□ 14. 卸载 Docker(可选)

13.10 本章小结

知识点要点
快速迁移alias docker=podman
命令兼容性95%+ 命令完全兼容
Socket 兼容podman.socket 提供 Docker API 兼容
Compose 兼容podman-compose 直接使用 compose.yaml
SELinux所有卷加 :Z
端口限制Rootless 使用 ≥ 1024 端口
CI/CD替换 docker 命令为 podman

下一步


扩展阅读