Linux 中 rootless Podman 调用离线 Docker Compose 二进制文件

1. 适用场景

本文适用于以下环境:

  • Linux 服务器不能直接访问互联网;
  • 使用普通用户运行 rootless Podman,全程不使用 sudo podman
  • 使用 Docker Compose 独立二进制文件作为 podman compose 的外部 Compose provider;
  • 示例安装位置为 ~/.local/bin/docker-compose
  • 文中版本号仅作示例,部署时可替换为经过验证且与目标环境兼容的版本。

podman compose 本身是一个包装命令。它会调用外部 Compose provider,再由该 provider 通过 rootless Podman API socket 管理容器。

2. 最终调用关系

普通用户执行 podman compose
        ↓
Podman 调用 ~/.local/bin/docker-compose
        ↓
docker-compose 连接 rootless Podman socket
        ↓
管理该普通用户自己的容器、镜像、网络和数据卷

rootless Podman 与 root 用户的 Podman 存储相互独立。因此,不要混用以下两种方式:

podman ...
sudo podman ...

3. 在联网电脑上准备离线文件

3.1 确认服务器 CPU 架构

在服务器执行:

uname -m

常见对应关系:

uname -m 输出 Compose 文件架构
x86_64 linux-x86_64
aarch64arm64 linux-aarch64linux-arm64

例如服务器输出 x86_64,应准备:

docker-compose-linux-x86_64

3.2 下载二进制文件

在可以联网的电脑上,从 Docker Compose Releases 页面下载目标版本对应的 Linux 二进制文件。

本文示例文件:

docker-compose-linux-x86_64

如发布页面提供校验文件,应同时下载并核对 SHA256。也可以先在联网电脑计算哈希并记录:

shasum -a 256 docker-compose-linux-x86_64

Linux 上可使用:

sha256sum docker-compose-linux-x86_64

将二进制文件通过 U 盘、内网文件服务器或 scp 复制到服务器。

例如通过 SSH 将文件复制到服务器当前用户的主目录:

scp docker-compose-linux-x86_64 your-user@server.example.com:~/

4. 以普通用户安装 Docker Compose

以下命令均以目标普通用户执行,不加 sudo

创建用户级命令目录:

mkdir -p ~/.local/bin

移动并重命名二进制文件:

mv ~/docker-compose-linux-x86_64 ~/.local/bin/docker-compose

增加执行权限:

chmod 755 ~/.local/bin/docker-compose

也可以使用:

chmod +x ~/.local/bin/docker-compose

两者在该场景下通常都能正常使用,但含义略有区别:

  • chmod 755 明确设置为所有者可读写执行,其他用户可读执行;
  • chmod +x 仅在现有权限基础上增加执行位,不重设其他权限。

检查文件类型和权限:

file ~/.local/bin/docker-compose
ls -l ~/.local/bin/docker-compose

验证二进制文件可以运行:

~/.local/bin/docker-compose version

预期输出类似:

Docker Compose version v5.3.0

如果出现 Exec format error,通常是下载的 CPU 架构不匹配。

5. 配置用户级 PATH

确认当前 PATH 是否包含 ~/.local/bin

echo "$PATH"
command -v docker-compose

如果找不到 docker-compose,将下面内容加入 ~/.profile

export PATH="$HOME/.local/bin:$PATH"

让配置立即生效:

. ~/.profile

再次验证:

command -v docker-compose
docker-compose version

预期路径类似:

/home/your-user/.local/bin/docker-compose

6. 启用 rootless Podman API socket

检查用户级 socket:

systemctl --user status podman.socket

启用并立即启动:

systemctl --user enable --now podman.socket

检查 socket 文件:

ls -l "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock"

查看实际监听地址:

systemctl --user show podman.socket -p Listen

典型地址为:

unix:///run/user/1000/podman/podman.sock

其中 1000 是当前用户 UID,可通过下面命令查看:

id -u

可选:退出 SSH 后仍保留用户服务

如果需要用户退出登录后继续保留 user systemd 服务,可由管理员执行一次:

sudo loginctl enable-linger user

这不是运行 Podman 容器时使用 sudo,而是管理员对该用户的 systemd linger 设置。

7. 指定 Compose provider 和 Podman socket

当前终端临时配置:

export PODMAN_COMPOSE_PROVIDER="$HOME/.local/bin/docker-compose"
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock"

说明:

  • PODMAN_COMPOSE_PROVIDER 指定 podman compose 调用哪个外部程序;
  • DOCKER_HOST 让 Docker Compose 连接当前用户的 Podman socket,而不是 /var/run/docker.sock

为了每次登录自动生效,将以下内容加入 ~/.profile

export PATH="$HOME/.local/bin:$PATH"
export PODMAN_COMPOSE_PROVIDER="$HOME/.local/bin/docker-compose"
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock"

重新加载:

. ~/.profile

检查变量:

printf '%s\n' "$PODMAN_COMPOSE_PROVIDER"
printf '%s\n' "$DOCKER_HOST"

8. 验证 Podman 调用 Docker Compose

先分别验证 Podman 和 Compose:

podman version
docker-compose version

再验证包装调用:

podman compose version

正常情况下会看到类似输出:

>>>> Executing external compose provider "/home/your-user/.local/bin/docker-compose". Please refer to the documentation for details. <<<<

Docker Compose version v5.3.0

这段提示不是错误,它表示 Podman 已经按配置调用外部 Docker Compose provider。

还可以验证 Compose 是否能够访问 Podman API:

docker-compose ls
podman ps -a

9. 离线部署镜像

在联网环境下载并导出镜像:

podman pull docker.io/library/alpine:latest
podman save -o alpine-latest.tar docker.io/library/alpine:latest

将镜像包复制到离线服务器后,以运行服务的同一个普通用户导入:

podman load -i alpine-latest.tar

检查镜像:

podman images

注意:如果使用 sudo podman load 导入,镜像会进入 root 用户的存储,普通用户执行 podman images 时看不到。

10. 使用 Compose 启动服务

进入 Compose 项目目录:

cd ~/compose-app

先检查最终解析结果:

podman compose config

后台启动:

podman compose up -d

如果应用提供了自己的管理脚本,应按照该应用的部署说明执行,而不是绕过脚本直接操作 Compose。

./service.sh up

查看状态:

podman ps -a
podman compose ps

查看日志:

podman compose logs --tail 100
podman logs --tail 100 容器名称

停止服务但保留数据卷:

podman compose down

不要随意使用 down -v,它会删除 Compose 管理的数据卷,可能造成业务数据丢失。

11. .env 文件注意事项

.env 通常包含密码和密钥,应限制权限:

chmod 600 .env

修改 .env 后,通常需要重新创建容器才能让新环境变量生效:

podman compose up -d --force-recreate

对于生产项目,应优先使用项目自带的管理脚本执行重建,避免漏掉覆盖文件、项目名或初始化步骤。

检查 Compose 展开结果时要注意:

podman compose config

该命令可能输出已经展开的敏感变量,不要将完整输出粘贴到公开渠道。

12. 常见问题排查

12.1 podman compose 找不到 provider

检查:

ls -l ~/.local/bin/docker-compose
command -v docker-compose
printf '%s\n' "$PODMAN_COMPOSE_PROVIDER"

重新设置:

export PODMAN_COMPOSE_PROVIDER="$HOME/.local/bin/docker-compose"

12.2 permission denied

增加执行权限:

chmod 755 ~/.local/bin/docker-compose

同时确认所在目录允许当前用户访问:

namei -l ~/.local/bin/docker-compose

12.3 Exec format error

检查服务器与文件架构:

uname -m
file ~/.local/bin/docker-compose

必须重新准备与服务器 CPU 架构一致的 Linux 二进制文件。

12.4 无法连接 Docker daemon 或 Podman socket

典型报错包含:

Cannot connect to the Docker daemon

检查并重启用户 socket:

systemctl --user restart podman.socket
systemctl --user status podman.socket

重新设置地址:

export DOCKER_HOST="unix://${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock"

验证 socket 是否存在:

test -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock" && echo OK

12.5 使用 SSH 非交互执行时找不到 docker-compose

非交互 shell 不一定读取 ~/.profile。可以使用绝对路径:

PODMAN_COMPOSE_PROVIDER="$HOME/.local/bin/docker-compose" podman compose version

项目脚本中也应优先使用明确路径或主动加载所需环境变量。

12.6 bind mount 源文件被创建成目录

如果 Compose 中声明:

volumes:
  - ./script.sh:/usr/local/bin/script.sh:ro

但服务器上 ./script.sh 不存在,容器工具可能把它创建成目录,导致容器内脚本无法执行。

检查:

file ./script.sh
ls -ld ./script.sh

正确结果应为普通文件,而不是 directory。应删除误建的空目录,再复制真实脚本文件:

rmdir ./script.sh
scp script.sh your-user@server.example.com:~/compose-app/script.sh
chmod 755 ./script.sh

12.7 容器一直显示 Waitingstarting

先检查状态和健康检查历史:

podman ps -a
podman inspect 容器名称 --format '{{json .State.Health}}'

查看日志:

podman logs --tail 200 容器名称

再检查同一网络内的 DNS 和端口连通性。Node.js 容器示例:

podman exec 容器名称 node -e "require('dns').lookup('服务名',(e,a)=>console.log(e||a))"

12.8 已存在的数据卷警告

可能看到:

volume "example_data" already exists but was not created by Docker Compose

如果该卷本来就是手动创建并需要复用,可在 Compose 文件中明确声明为外部卷:

volumes:
  example_data:
    external: true
    name: example_data

修改前应确认该数据卷确实属于当前项目,禁止为了消除警告而删除生产数据卷。

13. 推荐检查清单

部署前:

uname -m
podman version
file ~/.local/bin/docker-compose
docker-compose version
systemctl --user is-active podman.socket
podman compose version
podman images

启动后:

podman ps -a
podman compose ps
podman compose logs --tail 100

确认以下事项:

  • 所有命令都由同一个普通用户执行;
  • 没有混用 sudo podman
  • Compose 二进制文件架构正确并具有执行权限;
  • PODMAN_COMPOSE_PROVIDER 指向离线二进制文件;
  • DOCKER_HOST 指向当前用户的 Podman socket;
  • 所需镜像已导入普通用户的 rootless Podman 存储;
  • bind mount 的源文件和目录均真实存在;
  • 数据卷未被误删;
  • 关键容器健康检查均已通过。