适用:使用 systemd 的 Linux 服务器、常规 rootful Docker Engine;Compose 示例使用
docker composeV2。本文为教学故障场景,错误文本和结果为示意,具体输出以现场为准。
一、遭遇情况:本地构建成功,上传服务器却无法启动
开发电脑构建并上传镜像,服务器启动时报:
exec /app/server: exec format error
本篇主案例是 ARM64 开发环境生成了 ARM64 程序,却放到 AMD64 服务器执行。相似的错误还可能由没有正确解释器声明的入口脚本导致,所以不能看到这句话就立刻强加 --platform。
另一些错误是 no matching manifest、no such file or directory 或 permission denied,它们与架构问题可能相关,也可能属于完全不同的层次。
二、原因分析:镜像元信息与内部程序都要匹配
平台通常写成 linux/amd64 或 linux/arm64。宿主机内核、镜像所选平台和待执行程序需要兼容,或者环境确实提供合适的模拟执行能力。
多平台镜像索引可以包含多个平台的版本,让 Docker 选择匹配项。但如果 Dockerfile 复制了错误架构的预编译程序,即使镜像元数据写对,内部程序仍可能执行失败。

三、排查思路:从平台到可执行文件逐层核对
1. 检查主机、服务端与本地镜像
uname -m
docker info --format 'OS={{.OSType}} Arch={{.Architecture}}'
docker image inspect your-image:tag --format 'OS={{.Os}} Arch={{.Architecture}}'
docker buildx imagetools inspect your-registry.example.test/team/app:tag
your-image:tag 和仓库域名是占位符,替换为实际镜像。uname -m 常显示 x86_64 或 aarch64,与 Docker 平台里的 amd64、arm64 是常见对应关系。
远程 manifest 检查需要仓库网络和权限。它反映标签提供哪些平台,不一定等于这台服务器已经缓存的那个本地镜像,二者要分开核对。
2. 如果入口是脚本,再看文件格式
docker image inspect your-image:tag --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}'
如果第一条执行的是 /app/start.sh,检查源码中的首行、行尾格式、可执行权限,以及解释器是否存在。#!/bin/bash 放到没有 Bash 的 Alpine 镜像中,就可能报找不到文件。
脚本存在却提示 no such file or directory,还可能是 shebang 的解释器路径带了 CRLF 的回车字符,或二进制所需动态加载器不存在。看到“文件存在”,不能排除解释器和加载器缺失。
四、实践一:用正确平台重建一个简单服务
在新的实验目录保存以下 Dockerfile:
FROM python:3.12-alpine
WORKDIR /srv
CMD ["python", "-m", "http.server", "8000", "--bind", "0.0.0.0"]
假定目标服务器为 AMD64,执行:
target_platform=linux/amd64
docker buildx build --platform "$target_platform" -t dt15-web:fixed --load .
docker image inspect dt15-web:fixed --format '{{.Os}}/{{.Architecture}}'
docker run -d --name dt15-web -p 127.0.0.1:18095:8000 dt15-web:fixed
curl -I --max-time 5 http://127.0.0.1:18095/
ARM64 目标改为 linux/arm64,并在该目标或具备相应构建能力的 builder 上执行。--load 这里用于把单平台结果加载到本地镜像库,不应假设所有镜像存储后端都能用相同方式加载完整多平台结果。
真实应用若复制外部二进制,应同时修正编译目标。Go 可使用相应的 GOOS/GOARCH 交叉编译;带原生依赖的项目还要考虑动态库与基础镜像兼容,不能只改镜像标签。
五、实践二:辨认 Windows 行尾带来的脚本错误
以下在独立实验目录故意创建 CRLF 脚本,演示它与架构错误的区别:
lab_dir=$(mktemp -d /tmp/dt15.XXXXXX)
printf '#!/bin/sh\r\necho script-ok\r\n' > "$lab_dir/start.sh"
chmod 0755 "$lab_dir/start.sh"
docker run --rm --mount "type=bind,src=$lab_dir,dst=/lab,readonly" --entrypoint /lab/start.sh alpine:3.21
这次常见错误指向解释器找不到,而不一定显示 exec format error。如果系统启用 SELinux,还可能先遇到挂载标签问题,要按第 12 篇区分,不能把任何失败都算成 CRLF 已复现。
只修复这个实验脚本:
sed -i 's/\r$//' "$lab_dir/start.sh"
docker run --rm --mount "type=bind,src=$lab_dir,dst=/lab,readonly" --entrypoint /lab/start.sh alpine:3.21
预期得到 script-ok。也可以在代码仓库使用 .gitattributes 约定 *.sh text eol=lf,并在 Dockerfile 中正确复制文件和设置执行权限。
六、常见误区:--platform 不是自动修复器
docker run --platform linux/amd64 ... 表达选择的平台,不会把一个 ARM 程序转换成 AMD64。没有对应镜像项时,可能直接拉取失败;有项但机器不具备相应执行能力时,仍可能失败。
模拟执行可用于部分构建与测试,但有性能和兼容边界。服务器部署通常优先提供原生平台镜像,而不是在不理解环境的情况下强行依赖模拟。
不建议因为一条错误就统一替换所有基础镜像。先确认出问题的是平台、内部二进制、脚本格式、解释器,还是执行权限,再修改对应层。
七、验证与总结
验证包括主机平台与目标镜像匹配、实际入口可执行、容器稳定运行和业务请求成功。元数据相符但仍失败时,回到真正执行的文件,检查二进制与解释器依赖。
本篇收束整个系列的共同方法:从具体错误出发,用证据逐层缩小范围,做一处有理由的修复,再回到原始业务请求验证。能解释修复为什么有效,才比“重建后碰巧好了”更接近可靠运维。