之前很多读者私信问过一个问题:我们的容器服务还在用 Docker,团队里也有人提 Podman,说以后要切换,到底值不值得换?加上最近这两年,Docker Desktop 对大型企业的商业授权收紧,容器运行时也不再是 Docker 的“单极世界”,很多企业开始重新评估自己的容器技术栈。
这篇文章不打算站队,也不打算否定 Docker,而是从成本、安全、架构差异、迁移实操四个角度,完整梳理“企业为什么会考虑从 Docker 转向 Podman”这件事。全文包含可复制的安装命令、常用命令对比、运行示例和排错思路,无论是刚接触容器的新人,还是已经在生产环境使用 Docker 的运维、后端开发,都可以按章节查阅。
1. 背景:容器运行时为什么值得重新选型
1.1 Docker 依然是容器生态的“事实标准”,但不再是唯一答案
Docker 对容器技术的普及起到的作用是决定性的。它把 Linux 内核的 namespace、cgroup 等底层能力封装成易用的镜像、容器、命令行工具,让开发者可以像装应用一样分发和运行软件。即使到了今天,绝大多数关于容器的教程、CI/CD 脚本、开源项目,仍然默认使用 Docker 作为运行时。
但随着业务规模变大,Docker 的“守护进程架构”在某些场景下暴露了问题:
- 管理端权限过高。
- 守护进程成为中心故障点。
- 部分商业使用场景受到 License 限制。
- 企业内部对安全审计、供应链依赖的要求越来越严格。
于是,基于同一套 OCI 标准实现的 Podman 开始进入企业视野。
1.2 Podman 是什么
Podman(Pod Manager)是一个无守护进程的容器引擎,由 Red Hat 主导开发,兼容 OCI 镜像标准和 Dockerfile 规范。它可以运行 root 模式,也原生支持 rootless 模式;可以管理单个容器,也支持 Pod(容器组)概念,还能与 systemd 集成,让容器以系统服务的方式托管。
如果只看命令行,Podman 刻意保持了和 Docker 高度一致的使用体验。很多常用命令几乎可以直接替换:
docker pull nginx # 对应 podman pull nginxdocker run -d -p 8080:80 nginx # 对应 podman run -d -p 8080:80 nginx这也是很多团队能够低成本切换的直接原因。
1.3 这篇文章能解决什么问题
这篇文章会重点回答几个被反复讨论的问题:
- Docker 和 Podman 的架构差异到底在哪儿?
- 企业使用的“成本”差异,不光是 License,还包括运维和架构成本。
- 从安全角度,rootless 和无守护进程为什么更有优势?
- 如果决定迁移,现有 Docker Compose、容器卷、网络配置怎么平滑适配?
- Podman 有哪些高频坑,如何排查?
2. 核心架构差异:守护进程模型 vs 无守护进程模型
2.1 Docker 的 C/S 架构
Docker 采用传统的客户端-服务端架构。当我们执行docker命令时,实际是 docker CLI 客户端向 dockerd 守护进程发送请求,由 dockerd 负责拉取镜像、创建容器、管理网络等。
graph TD A[docker CLI] --> B[dockerd] B --> C[containerd] C --> D[runc] D --> E[容器进程]这种架构的好处是:
- 职责集中,管理能力完整。
- 与其他系统集成方便,很多工具直接对接 Docker API。
- 生态成熟,文档和故障案例丰富。
但缺点也很明显:
- dockerd 本身是一个特权进程,一旦被攻破,等于向攻击者交出了宿主机的控制权。
- dockerd 一旦挂掉,依赖它的容器调度、网络管理、健康检查都会受到影响。
- 所有客户端都走同一个守护进程,容易出现权限边界不清的问题。
另外,很多人会把 Docker 整体等同于开源,其实 Docker 的项目分成了多个部分。Docker Engine、Docker CLI 这些核心组件是开源且免费的,但 Docker Desktop 对大型企业是商用收费的。这一点我们后面再展开。
2.2 Podman 的 fork-exec 无守护进程架构
Podman 没有独立的守护进程。当你执行podman run时,Podman CLI 进程会直接与镜像仓库、存储驱动和容器运行时打交道,并通过 fork-exec 方式拉起容器进程。换句话说,Podman 进程结束后,容器由直接子进程继续托管,而不是由一个常驻守护进程统一调度。
$ podman run -d --name web nginx这条命令的执行路径大致是:
podman CLI -> 读取本地镜像/配置 -> 创建容器配置 -> fork 出容器进程(通过 crun/runc) -> 返回容器 ID这种设计带来的核心优势:
- 没有常驻 root 守护进程,减少了攻击面。
- 普通用户可以运行 rootless 容器,不直接接触宿主机 root 权限。
- 容器生命周期和 systemd 集成更方便,可以生成对应的 systemd unit 文件。
- 单条命令失败不会影响其他容器。
2.3 容器运行时组件对比
| 对比项 | Docker | Podman |
|---|---|---|
| 架构模型 | 客户端-服务端(daemon) | 无守护进程(daemonless) |
| 与 OCI 兼容镜像 | 支持 | 支持 |
| Dockerfile 构建 | 支持 | 支持 |
| rootless 容器 | 支持但配置麻烦,需要配置 dockerd 和 slirp4netns 等 | 原生支持,用户命名空间隔离 |
| systemd 集成 | 需要额外工具或手动脚本 | 内置podman generate systemd |
| Pod 概念 | 通过 docker-compose 间接实现 | 原生支持 Pod |
| SELinux 支持 | 有,但历史配置坑多 | 与 Red Hat 系系统绑定更深,默认策略完整 |
| Docker Compose | 原生支持 | 通过podman compose或podman-compose兼容 |
3. 成本视角:企业从 Docker 转向 Podman 到底省了什么
3.1 商业授权变化
Docker 的收费策略经历过多次调整。目前 Docker Engine 本身仍然是开源项目,可以免费使用,但 Docker Desktop 对大型企业(员工数超过 250 人,或年收入超过 1000 万美元)使用是收费的。很多企业内部并不需要桌面版 Docker,主要用的是 Linux 服务器上的 Docker Engine,这部分成本增速其实没有想象中那么大。
但问题在于,很多团队已经习惯了用 Docker Desktop 做本地开发,团队成员一旦超过规模,License 成本是不可忽视的。Podman Desktop 是社区开源桌面工具,配合 WSL2 或 Linux 虚拟机也能提供类似 Docker Desktop 的图形化体验,成本上更可控。
3.2 运行时依赖与维护成本
Docker 的安装和升级涉及多个组件:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
- docker-buildx-plugin
每次升级如果版本对齐不好,可能出现组件不匹配的问题。而 Podman 的安装相对集中:
# Ubuntu / Debian sudo apt update sudo apt install -y podman podman-compose# CentOS / RHEL / Fedora sudo dnf install -y podman podman-compose这里不是说 Podman 没有依赖,而是它的组件边界更清晰,普通升级不容易出现“docker 命令存在但 daemon 起不来”这种断裂问题。
3.3 多主机管理和编排成本
在 Kubernetes 已经成为主流调度平台之后,Docker 的 Swarm 模式使用率明显下降。现在多数企业跑 Kubernetes 时,kubelet 的容器运行时默认使用 containerd,而不是 Docker。也就是说,很多企业对 Docker 的依赖已经局限在开发、镜像构建、单机容器运维这几个环节。
在这些环节中,Podman 的无守护进程模型可以减少一层中间组件。开发机上不需要再常驻一个 dockerd,也就不存在“Docker Desktop 一直 starting”“docker service start 失败”“Docker daemon 没有权限”这类日常问题。
3.4 成本对比总结
| 项目 | Docker | Podman |
|---|---|---|
| Docker Engine 本体 | 开源免费 | 开源免费 |
| 图形化管理工具 | Docker Desktop 对大企业收费 | Podman Desktop 社区开源 |
| 守护进程资源占用 | 常驻 dockerd / containerd | 无常驻守护进程 |
| 系统集成开发成本 | 需要对接 Docker API | 可直接由 systemd 托管 |
| 迁移成本 | - | 命令兼容度高,alias 即可快速过渡 |
需要说明的是,成本降低不等于零成本切换。如果企业已经深度使用 Docker Compose、私有插件、自定义网络插件,迁移过程中仍然需要投入测试和改造时间。
4. 安全视角:rootless、无守护进程和供应链安全
4.1 Docker 的“docker 组等于 root” 问题
Docker 安装完成后,默认会创建一个docker组。凡是加入docker组的用户,都可以执行docker run -v /:/host这类高危命令,等价于直接拿到宿主机 root 权限。这在多数企业里是很大的安全隐患,因为开发环境、测试环境往往共用一台服务器,一旦某个开发账号被提权,攻击路径会被放大。
严格来说,这不是 Docker 设计上的 bug,而是容器能力模型本来就被特别大。但客观上,很多中小型团队没有能力做细粒度的权限网关,导致docker组权限被滥用。
4.2 Podman 的 rootless 模式如何降低风险
Podman 在设计之初就将 rootless 作为一等公民。普通用户不需要进入docker组,不需要修改/etc/sudoers,也不用接触 root 守护进程,就能运行自己的容器。
实现原理主要依赖 Linux 用户命名空间(user namespace)。宿主机的一个普通用户,在容器内部可以映射为 UID 0,也就是容器内的 root,但这个 root 对应到宿主机上依然是一个无特权用户。
# 普通用户查看 podman 容器 $ id uid=1000(dev) gid=1000(dev) groups=1000(dev) # 通过 podman 运行容器 $ podman run --name test -d nginx:latest $ podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES abc123 docker.io/library/nginx:latest nginx -g 'daemon o... 10 seconds ago Up 10 seconds test此时test容器内的 root 并不是宿主机上的 root 用户。即使容器被攻破,攻击者在宿主机上能做的操作也非常有限。
4.3 无守护进程带来的攻击面差异
Docker 的守护进程监听接口主要有三类:
/var/run/docker.sock本机 socket。- 配置了 TCP 监听时,暴露远程 API。
- 插件系统、网络代理等辅助进程。
历史上出现过多次与 Docker API 未授权访问相关的攻击事件。只要某个容器拿到了 docker.sock 的挂载权限,就相当于拿到了宿主机权限。这类攻击在 Podman 的无守护进程模型中天然不存在,因为根本没有一个全局的 root 守护进程可供攻击。
4.4 镜像安全和供应链
镜像供应链安全是另一个重要维度。Docker Hub 上确实存在大量镜像,但如果直接docker pull并运行,镜像来源、签名校验、漏洞扫描都容易成为盲区。Podman 原生支持 Sigstore 签名校验,可以配置强制要求镜像带签名才能拉取。
例如在/etc/containers/registries.conf.d/secure.conf中配置:
[[registry]] location = "registry.example.com" insecure = false结合企业内网镜像仓库(如 Harbor、Quay)使用,可以统一实现镜像扫描、签名、审计策略。
5. 迁移实操:从 Docker 到 Podman 的完整路径
5.1 安装 Podman
如果是在 Ubuntu 上使用,可以直接用 apt 安装:
sudo apt update sudo apt install -y podman podman-compose如果是在 CentOS / Rocky Linux / AlmaLinux 上:
sudo dnf install -y podman podman-compose安装完成后验证版本:
podman --version预期输出类似:
podman version 4.9.4如果你的系统版本较老,建议优先使用发行版官方源,或者 Red Hat 提供的 podman 安装配置来获取较新版本。
5.2 让docker命令指向 Podman
最快速的方式是创建一个 shell alias:
alias docker=podman为了长期使用,可以写进~/.bashrc或~/.zshrc:
echo "alias docker=podman" >> ~/.bashrc source ~/.bashrc之后执行:
docker versionPodman 会在输出中显示兼容层信息。多数常用 Docker 命令都可以通过这种方式平滑过渡,例如docker run、docker ps、docker images、docker exec。
5.3 Docker Compose 兼容方案
企业项目里最常见的编排文件是docker-compose.yml。Podman 有两种兼容方式:
方式一:使用podman-compose,它是一个 Python 实现的 Docker Compose 兼容工具:
podman-compose up -d方式二:使用podman compose,需要系统中安装了docker-compose或podman-docker插件,Podman 会调用兼容层:
podman compose up -d以一个常见的 MySQL 8.0 + Redis 组合为例:
# docker-compose.yml version: "3.9" services: mysql: image: mysql:8.0 container_name: mysql-test ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: testdb volumes: - mysql-data:/var/lib/mysql redis: image: redis:7-alpine container_name: redis-test ports: - "6379:6379"; volumes: mysql-data:保存后执行:
podman compose up -d如果系统提示找不到compose子命令,需要先安装docker-compose:
sudo pip3 install docker-compose # 或者 sudo dnf install docker-compose-plugin5.4 使用 Podman 生成 systemd 服务
Podman 在生产环境中的典型优势是可以把容器托管给 systemd。使用以下命令生成 service 单元:
podman generate systemd --name mysql-test --files --new这时目录下会生成类似container-mysql-test.service的文件。把它复制到 systemd 目录:
sudo cp container-mysql-test.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now container-mysql-test这样容器就可以随宿主机开机自启,并且由 systemd 负责管理和重启策略。在生产环境里,这比依赖 Docker daemon 的 restart policy 更符合 Linux 系统管理的直觉。
5.5 构建镜像时的差异
Docker 构建镜像常用docker build,Podman 通过podman build提供同样能力,底层使用 Buildah 的部分实现,支持 Dockerfile 的绝大多数指令。
podman build -t myapp:latest .如果构建过程中遇到需要 BuildKit 高级特性的功能,例如特定缓存挂载语法,需要检查 Podman 版本是否支持。Podman 4.x 以后支持--build-arg、多阶段构建、BuildKit 风格的 cache 挂载等常见用法,但具体格式仍需结合版本验证。
5.6 镜像仓库源配置
很多国内用户拉取镜像时遇到过“镜像下载慢”或超时问题。Docker 通常需要改/etc/docker/daemon.json;Podman 则统一配置在/etc/containers/registries.conf或/etc/containers/registries.conf.d/下。
以配置国内镜像加速为例,新建/etc/containers/registries.conf.d/accelerate.conf:
[[registry]] location = "docker.io" [[registry.mirror]] location = "mirror.gcr.io"配置完成后,执行拉取:
podman pull docker.io/library/mysql:8.0如果你的网络环境需要走内部代理,也可以在/etc/containers/registries.conf中设置http_proxy、https_proxy等环境变量,但要注意容器拉取使用的是 Podman 所在宿主机的网络,不是容器内部网络。
6. 实际运行示例:用 Podman 替代 Docker 启动 MySQL 8.0
下面用一个完整的例子说明如何从零开始,用 Podman 代替 Docker 启动 MySQL 8.0,并完成数据持久化、端口映射、进入容器验证等步骤。
6.1 拉取镜像
podman pull mysql:8.0如果本地没有配置加速源,这一步可能比较慢。成功后会显示:
Getting image source signatures Copying blob sha256:... ... Writing manifest to image destination6.2 运行容器
podman run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=testdb \ -v mysql8_data:/var/lib/mysql \ mysql:8.0参数说明:
-d:后台运行。--name mysql8:指定容器名称。-p 3306:3306:宿主机 3306 映射到容器 3306。-e:设置环境变量,初始化 root 密码和默认数据库。-v mysql8_data:/var/lib/mysql:使用命名卷持久化数据,容器删除后数据仍在。
6.3 查看容器状态
podman ps预期输出:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1b2c3d4e5f6 docker.io/library/mysql:8.0 mysqld 10 seconds ago Up 10 seconds 0.0.0.0:3306->3306/tcp mysql86.4 进入容器并连接 MySQL
podman exec -it mysql8 bash容器内执行:
mysql -uroot -proot123看到如下输出说明容器正常:
Welcome to the MySQL monitor. Commands end with ; or \g. ... mysql>6.5 验证数据持久化
删除容器再重新创建,数据依然存在:
podman rm -f mysql8 podman run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=testdb \ -v mysql8_data:/var/lib/mysql \ mysql:8.0登录后查询:
SHOW DATABASES;可以看到testdb仍然存在。
6.6 导出和导入镜像
Docker 中常用docker save/docker load迁移镜像,Podman 对应:
podman save -o mysql8.tar mysql:8.0 podman load -i mysql8.tar7. 常见问题与排查思路
7.1docker: command not found
即使配置了 alias,也没有生效。原因通常是 shell 配置文件没有重新加载,或者当前 shell 不支持 alias 展开。
source ~/.bashrc alias docker=podman如果是非交互 shell 或 CI 环境,建议直接使用podman命令,不要依赖 alias。
7.2permission denied while trying to connect to the Docker daemon socket
Docker 环境最常见的报错:
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock根本原因是当前用户不在docker组,或者 daemon 未启动。
但在 Podman 环境下,这种报错一般不会出现,因为不带 sudo 的普通用户默认走 rootless 模式。如果仍然遇到权限问题,优先检查用户目录权限:
ls -ld ~/.local/share/containers sudo chown -R $(id -u):$(id -g) ~/.local/share/containers7.3 Podman 启动后容器无法访问网络
现象是容器能启动,但无法访问外网。常见原因是 rootless 容器的网络转发依赖 slirp4netns 或 pasta,如果系统没有安装这些组件,网络模式会受限。
sudo apt install -y slirp4netns # 或 sudo dnf install -y slirp4netns也可以尝试指定网络模式:
podman run --network=host --rm alpine ping -c 3 8.8.8.8host模式直接使用宿主机网络栈,适合排查是否网络转发组件问题。
7.4 端口无法从外部访问
rootless 容器绑定端口时,如果端口小于 1024,普通用户没有权限绑定,必须使用大于等于 1024 的端口,或者通过sysctl授权。
对于生产环境更推荐的做法是:用 systemd 托管 rootless 容器,并在系统层配置端口转发,例如 firewalld 或 nftables。
7.5 rootless 容器写宿主目录权限不足
当把宿主机目录挂载进容器时:
podman run -v /home/dev/app:/app:Z alpine ls -l /app:Z是 SELinux 标签自动修正选项。如果遇到Permission denied,可以检查宿主机目录权限,并考虑使用 podman unshare 修正属主:
podman unshare chown -R 1000:1000 /home/dev/app7.6 GitHub Actions 或 CI 中 Docker 命令不可用
CI 环境如果想从 Docker 切换 Podman,需要安装 Podman,然后在 runner 中设置别名。GitHub 官方不建议在 macOS runner 上直接运行 Podman 容器,建议在 Linux runner 中执行。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| docker 命令不存在 | 未安装或 alias 未生效 | 安装 podman 或配置 alias |
| 权限不足 | 用户不在 docker 组 | 使用 Podman rootless 模式 |
| 网络不可用 | slirp4netns 缺失 | 安装网络组件或改用 host 网络 |
| 端口无法访问 | rootless 绑定低端口受限 | 使用高端口或使用 systemd 托管 |
| 镜像拉取慢 | 未配置镜像加速 | 修改 registries.conf |
| SELinux 拒绝挂载 | 安全标签冲突 | 挂载参数加 :Z |
8. 最佳实践与企业落地建议
8.1 不要一步到位,分阶段切换
从 Docker 迁移到 Podman,最忌讳的就是把生产环境所有节点一次性切换。建议先做三层推进:
- 开发机先行:开发人员在本地使用 Podman 验证日常流程,判断命令兼容度。
- CI 环境验证:把镜像构建和单元测试从 Docker 切换到 Podman。
- 生产单节点试点:选择非核心服务验证 systemd 托管、重启策略、日志采集。
8.2 统一镜像仓库和安全策略
无论使用哪种运行时,镜像仓库都应该是企业统一管控的。推荐使用 Harbor 或 Quay 搭建内网仓库,并开启镜像扫描和签名校验。Podman 配置中强制只允许从内网仓库拉取镜像:
[[registry]] location = "docker.io" blocked = true[[registry]] location = "harbor.internal.example.com" insecure = false这样能避免开发者随意从外网拉取未知镜像。
8.3 rootless 模式应作为默认模式
企业里的开发机、测试机、甚至部分生产节点,都应该优先使用 rootless 容器。这不仅能降低权限风险,还能让普通用户自行管理属于自己的容器,减少运维介入。
但要注意 rootless 模式下的资源限制、日志收集、监控方案要提前验证。有些监控 agent 需要在宿主机读取容器运行时目录,rootless 模式下的路径不同,需要调整配置。
8.4 systemd 托管代替 restart policy
Podman 的--restart always也可以使用,但在企业环境更推荐结合 systemd。这样容器的启动顺序、资源限制、崩溃重启策略都归 systemd 管理,和宿主机整体状态保持一致。
8.5 日志、监控和审计
迁移到 Podman 后,日志路径会发生变化。Docker 日志默认在/var/lib/docker/containers,Podman rootless 日志通常在~/.local/share/containers/storage或~/.local/share/containers。日志采集 agent 需要使用podman logs或直接读取对应路径,同时注意 rootless 用户的目录权限。
监控层面,无论使用 Prometheus 的 cAdvisor,还是其他运行时指标采集器,都要确认新版是否支持 Podman 的 socket 接口。部分项目已经通过 Podman API 兼容层接入,但版本差异仍然存在,建议先在测试环境验证指标完整性。
8.6 什么情况下不建议马上切换
如果团队对 Docker 的依赖很深,例如:
- 大量使用 Docker Compose v2 的高级特性。
- 依赖 Docker BuildKit 的特殊缓存语法构建生产镜像。
- 使用了未改造的私有化插件、网络方案。
- 团队容器经验有限,没有足够时间和人力做兼容测试。
这种情况不建议强制切换。可以先保持 Docker Engine + 企业 License 评估的组合,等 Podman 在团队内部积累更多实践后,再逐步扩大范围。
8.7 保持学习,关注容器运行时演进
容器技术栈的变化比很多人想象中要快。Kubernetes 1.24 以后正式移除 dockershim,大量云厂商默认运行时已经转向 containerd;在单机场景,Podman 又提供了更贴合 Linux 原生习惯的替代方案。对开发者来说,真正重要的不是绑定某一个工具,而是理解 OCI 镜像、容器运行时、容器编排这些抽象层之间的关系。
掌握 Podman 后,你会发现它和 Docker 并不是“替换”关系,更像是在同一个开放标准下的另一种实现。Docker 的生态和文档积累依然是宝贵资源,而 Podman 则提供了更安全、更轻量的选择,尤其在 Red Hat 系系统和多租户环境中表现突出。
9. 总结
这篇文章从架构差异、成本、安全、迁移实操、常见问题几个角度,完整拆解了企业为什么会在 Docker 之外认真考虑 Podman。
回到最初的问题:企业是否应该“弃用 Docker 转投 Podman”?我的建议是不必急着给出非黑即白的答案。如果你的团队正在从零搭建容器环境,直接选择 Podman 可以减少守护进程带来的复杂性,并获得更安全的 rootless 默认策略;如果团队已经深度使用 Docker,建议先在开发环境和 CI 中做兼容验证,用 alias 过渡,再逐步迁移。
实际动手时,建议从一条最简单的命令开始:
podman run --rm hello-world跑通之后,再用 Podman 启动一个 MySQL、一个 Redis,搭建一套完整的本地开发环境。等命令和镜像构建流程都习惯了,再思考是否需要把生产节点切过来。
如果你正在做容器运行时的选型,希望这篇文章能帮你建立一个更完整的决策框架。后续我也会持续整理 Podman 在生产环境落地、镜像签名、与 Kubernetes 集成相关的实战内容,欢迎收藏备用。
如果本文对你有帮助,可以收藏、转发,也可以在评论区聊聊你所在团队的容器运行时现状。