本文是使用 Apple Silicon Mac 将应用移植到懒猫商店的踩坑记录,希望能够给大家带来帮助。
Apple Silicon 很好,这几年几乎带动了 ARM 生态。但这也让 ARM 和 x86 的兼容问题更加常见,开发者需要同时照顾两种架构。就比如用来打包的 Docker 镜像,尽管编程语言和操作系统在底层屏蔽了不少硬件差异,容器镜像仍然需要匹配运行平台的架构。
这是我之前移植开源项目时,忘记构建不同架构的镜像,直接推送到懒猫镜像仓库导致的问题。Apple Silicon Mac 默认构建的是 ARM64 镜像,无法直接在 x86 机器上运行。
1 | pg-docker run -p 5000:5500 registry.lazycat.cloud/u04123229/you/doudizhu-scorer:d1d9085174c0bf8c |
由于打包时容器一直反复重启,在 Dozzle 里没有看到明显报错。一个直接的排查办法是通过 SSH 登录,再在终端执行 pg-docker run,这样所见即所得。Dozzle 地址为 https://dev.<name>.heiyu.space/dozzle/。
于是重新打包。跨架构构建需要使用 Buildx,当然前提是镜像里的运行时和代码本身也支持对应平台。
先看概念和原理:
buildx 是 Docker 提供的一种扩展功能,它基于 BuildKit 引擎,目的是为 Docker 提供更强大的构建功能,包括:
- 跨平台构建:支持在一种平台上构建适用于多种平台的 Docker 镜像。
- 缓存管理:支持高效的缓存管理机制,能够减少重复构建的时间。
- 多阶段构建:支持复杂的多阶段构建流程。
buildx 使 Docker 能够生成多平台的镜像,这意味着你可以在一个平台上(例如 ARM 或 x86)构建适用于其他平台(如 x86_64、arm64、armv7 等)的 Docker 镜像。
docker buildx build 通过指定 --platform 参数来告诉 Docker 在构建时要生成哪些平台的镜像。例如,linux/amd64 和 linux/arm64 就分别对应 x86 和 ARM 架构。
在 buildx 构建完成后,你得到的不是一个单独的镜像,而是一个支持多平台的 manifest list,这个列表包含了不同架构的镜像。这个列表可以推送到 Docker Hub 等镜像仓库,客户端在拉取时,会根据自己的硬件架构自动选择合适的镜像。
这意味着,我们可以通过同一个镜像标签(如 your_image_name)来支持多个平台的 Docker 镜像,而用户在拉取时会自动选择适合自己平台的镜像。
- 准备构建环境:
Docker Buildx 会首先准备并选择一个构建器(builder)。这个构建器负责在指定的平台上执行构建任务。 - 选择平台:
使用--platform参数来选择目标平台,Docker 会通过 QEMU 模拟器或者本地平台来执行构建。 - 构建镜像:
在选择平台后,Buildx 会根据 Dockerfile 和其他构建上下文开始构建镜像。它会处理平台特定的依赖和构建步骤。 - 生成适配镜像:
对于每个平台,Docker Buildx 会生成一个特定的镜像。例如,对于linux/amd64和linux/arm64,它会分别为这两个平台构建独立的镜像,并将它们绑定在一个 manifest list 中。 - 推送镜像:
完成构建后,你可以使用--push参数将包含多个架构镜像的 manifest list 推送到 Docker Hub 或其他镜像仓库。这个清单包含多个平台的镜像,当用户从仓库拉取时,Docker 会自动选择与当前平台兼容的镜像。
然后来实操
- 确保 Docker 版本支持 Buildx,用
docker buildx version验证。 - 创建并使用新的 Builder
- 打包的时候加上平台参数:
--platform linux/amd64,linux/arm64
具体命令如下:
1 | docker buildx version |
在这个过程中,我们可能还会使用 docker tag。这个命令可以给现有镜像添加新的标签(tag),通常用于修改镜像名称或指定版本,方便后续推送到 Docker Hub 或其他镜像仓库。
1 | docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG] |
- **
SOURCE_IMAGE[:TAG]**:要打标签的源镜像。TAG是可选的,如果不指定,默认是latest。 - **
TARGET_IMAGE[:TAG]**:新的目标标签,通常你可以为镜像指定一个新的名字或版本号。
假设你有一个名为 my_image:latest 的镜像,并且你希望将它标记为属于你自己(例如,your_dockerhub_username/my_image:latest):
1 | docker tag my_image:latest your_dockerhub_username/my_image:latest |
这条命令会将 my_image:latest 镜像打上 your_dockerhub_username/my_image:latest 的标签。
推送到 Docker Hub 之后,就可以像往常一样使用 Docker 了。
1 | docker pull your_dockerhub_username/your_image_name |
如果想通过 GitHub Actions 一键构建镜像,可以这样写:
1 | name: Build and Push Docker Image |
没有把 lzc-cli 写进去,是因为目前只能从终端查看推送到懒猫仓库的镜像命令,还不能把结果保存到一个中间位置,所以这里只做了通用版本。
事情到这里本来应该结束的,但似乎有了新的故事。
故事 1:无法打包
某次在打包过程中突然报错,一度以为 OrbStack 出现了问题,于是卸载重装、重启电脑,均无效。GPT 和 DeepSeek 也只是让我检查网络连接,期间重新执行 docker pull 也没有问题。
1 | characters ERROR: failed to dial qRPC: rpc error:code = Internal desc = rpc error: code = Internal desC = header key "x-docker-expoSe-session-name" contains value with non-printable ASCI #2793 |
无奈只能搜索,在 Issue 里看到一条评论:构建目录不能出现中文。(我的系统默认使用中文。)参考链接:https://github.com/docker/buildx/issues/2793
故事 2:构建之后没有输出
docker buildx build --platform linux/amd64 -t your_image_name .
1 | WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load |
这是使用 docker buildx 构建镜像时,指定了 docker-container 驱动,但是没有使用 --push 或 --load 参数。结果是,构建的镜像只会保留在构建缓存中,而不会被推送到镜像仓库或加载到本地 Docker 环境中。
我们可以通过两种方式之一来明确指定输出目标,避免出现此警告:
1. 使用 --push 将镜像推送到远程仓库:
如果你希望构建的镜像推送到 Docker Hub 或其他 Docker 镜像仓库,可以使用 --push 参数。例如:
1 | docker buildx build --platform linux/amd64 -t your_image_name --push . |
这将把镜像推送到 Docker 仓库,而不是仅保留在本地构建缓存中。
2. 使用 --load 将镜像加载到本地 Docker 环境:
如果你想将构建的镜像加载到本地 Docker 环境中以便后续使用(例如运行容器),可以使用 --load 参数:
1 | docker buildx build --platform linux/amd64 -t your_image_name --load . |
这会将构建的镜像加载到本地 Docker 环境,使你可以在本地运行、调试或进行其他操作。
故事 3:无法同时保存双平台 Image 到本地
1 | docker buildx build --platform linux/amd64,linux/arm64 -t cloudsmithy/shuangpin:latest . --load |
--load 只适用于单平台构建。如果你在跨平台构建(如 linux/amd64,linux/arm64)时使用 --load,则无法把完整的多平台 manifest list 加载到本地 Docker。跨平台构建时,通常需要使用 --push 将所有平台的镜像推送到远程仓库。
1 | docker buildx build --platform linux/amd64 -t cloudsmithy/shuangpin:latest . --load |
使用 --load 时,镜像会被加载到本地 Docker 守护进程中。对于大镜像,加载过程可能需要较长的时间和较多的本地存储空间。因此,如果镜像非常大,可能需要考虑是否使用 --push 直接推送到远程仓库,而不是将其加载到本地。
1 | docker buildx build --platform linux/amd64,linux/arm64 -t cloudsmithy/shuangpin:latest . --push |
故事 4:懒猫仓库黑魔法
对了,文档里提到懒猫 Registry 不能在微服外部直接使用,限制其实来自认证;未携带凭证时会直接返回 401。
1 | docker run -p 5000:5500 registry.lazycat.cloud/u04123229/you/doudizhu-scorer:d1d9085174c0bf8c |
最后
以上都是我自己撞过的坑。移植开源项目到懒猫商店,跨平台构建和推镜像这两步最容易卡住,照着排一遍应该能少绕点路。
扫码关注「忘机山人」


