# 官方镜像示例nginx:1.28.0ubuntu:22.04python:3.9-slim# 此处一定要有一行空行6.5 编写 sync.sh (核心同步脚本)将以下内容粘贴到 sync.sh 文件中。这个脚本会读取 images.txt 中的列表,逐一拉取镜像,打上 ACR 标签,并推送到你的 ACR 仓库。脚本中包含判重逻辑。 #!/bin/bashset -eux # -u: 遇到未定义变量报错;-e: 任何命令失败立即退出;-x: 打印执行的命令 IMAGES_FILE="images.txt" # 检查配置文件和镜像列表文件是否存在if [ ! -f "$IMAGES_FILE" ]; then echo "Error: images.txt not found! Please create it with a list of images to sync." exit 1fi # 检查必要的配置变量是否已设置if [ -z "$ACR_REGISTRY" ] || [ -z "$ACR_NAMESPACE" ]; then echo "Error: ACR_REGISTRY or ACR_NAMESPACE not set in github variables. Please check your config." exit 1fi echo "Starting Docker image synchronization to ACR..."echo "Target Registry: ${ACR_REGISTRY}"echo "Target Namespace: ${ACR_NAMESPACE}"echo "-----------------------------------" # 遍历 images.txt,逐行处理镜像while IFS= read -r image; do # 跳过空行或以 # 开头的注释行 if [[ -z "$image" || "$image" =~ ^# ]]; then continue fi echo "--- Processing image: ${image} ---" # 分离原始镜像的仓库名和标签 # 例如:nginx:latest -> original_repo=nginx, original_tag=latest # 例如:jenkins/jenkins:lts -> original_repo=jenkins/jenkins, original_tag=lts original_repo=$(echo "$image" | cut -d ':' -f1) original_tag=$(echo "$image" | cut -d ':' -f2) # 构造目标 ACR 完整镜像路径 target_full_image_path="${ACR_REGISTRY}/${ACR_NAMESPACE}/${original_repo}:${original_tag}" echo "Original image full path: ${image}" echo "Target ACR image full path: ${target_full_image_path}" # 检查阿里云仓库是否已有该tag # docker manifest inspect 命令用于检查远程 Registry 中的镜像是否存在。 # 如果已经存在,则跳过本次同步,避免重复操作和不必要的流量消耗。 if docker manifest inspect "${target_full_image_path}" > /dev/null 2>&1; then echo "${target_full_image_path} 已存在于 ACR,跳过本次同步。" echo "-----------------------------------" continue # 跳过当前循环的后续步骤 fi echo "Image ${target_full_image_path} not found in ACR. Proceeding with sync..." # 拉取原始镜像 echo "Pulling original image: ${image}..." docker pull "${image}" # 打上阿里云 ACR 的标签 echo "Tagging image ${image} to ${target_full_image_path}..." docker tag "${image}" "${target_full_image_path}" # 推送到阿里云 ACR echo "Pushing image ${target_full_image_path} to ACR..." docker push "${target_full_image_path}" # 清理本地拉取和打标签的镜像,释放 GitHub Actions Runner 的磁盘空间 echo "Cleaning up local images..." # 使用 || true 即使删除失败也不会中断脚本,确保后续镜像能继续处理 docker rmi "${image}" || true docker rmi "${target_full_image_path}" || true echo "Successfully synced: ${image} to ${target_full_image_path}" echo "-----------------------------------" done < "$IMAGES_FILE" echo "All specified images processed successfully."echo "Synchronization process finished."
6.6 提交代码到 GitHub 仓库完成文件创建和内容粘贴后,将这些文件提交到你的 GitHub 仓库: git add .git commit -m "Add workflow to sync Docker images to ACR"git push origin main
6.7 查看GitHub Actions同步状态代码推送后,GitHub Actions 将根据 sync.yml 中定义的触发器(push 或手动 workflow_dispatch )自动运行 Workflow。 你可以在 GitHub 仓库页面的 “Actions” 选项卡中查看 Workflow 的运行状态和日志。

6.8 Aliyun ACR查看镜像同步结果当 Workflow 成功运行后,你可以登录到 Aliyun ACR 控制台,进入你的命名空间,查看已同步的镜像。

6.9 后期增加镜像同步后续需要同步新的镜像时,只需修改 images.txt 文件,在其中添加新的镜像名和标签,然后提交到 GitHub 仓库即可。GitHub Actions 会自动检测 images.txt 的变更并触发同步。 脚本会自动判重:如果 ACR 中已经存在相同 acr_repo_name:original_tag 的镜像,脚本会跳过该镜像的同步。这意味着你可以保留 images.txt 中已同步的镜像,无需手动清理。如果需要强制更新某个镜像(即使标签相同,内容可能已更新),你需要手动从 ACR 中删除旧的镜像,或者修改 sync.sh 脚本的判重逻辑。
7. 使用 Aliyun ACR 镜像一旦镜像成功同步到 Aliyun ACR,你就可以在你的本地开发环境或国内的 CI/CD 流水线中,高速、稳定地拉取这些镜像。 7.1 登录阿里云 ACR:docker login --username=your_name registry.cn-hangzhou.aliyuncs.com# 详见设置访问凭证中的docker login
7.2 拉取镜像:根据你在 `images.txt` 中配置的原始镜像名,以及 `sync.sh` 脚本的命名转换规则(将 `/` 替换为 `-`),构造完整的 ACR 镜像路径进行拉取。例如,如果你同步了 `langgenius/dify-api:1.3.0`:```shell# 对于 langgenius/dify-api:1.3.0,ACR 中的名称会变成 langgenius-dify-api:1.3.0# 请将 your_namespace 替换为你在 ACR 中创建的命名空间docker pull registry.cn-hangzhou.aliyuncs.com/your_namespace/langgenius-dify-api:1.3.0```如果同步了 `nginx:1.28.0`:```shelldocker pull registry.cn-hangzhou.aliyuncs.com/your_namespace/nginx:1.28.0```
7.2 (可选但推荐) 创建本地 Tag,以便在 Dockerfile 或 docker-compose.yml 中使用原始镜像名:如果你希望在 Dockerfile 中继续使用 FROM langgenius/dify-api:1.3.0 这样的原始镜像名,或者在 docker-compose.yml 中直接引用,你可以在拉取到 ACR 镜像后,为它创建一个本地 Tag。 # 假设你已经成功拉取了 ACR 镜像# docker pull registry.cn-hangzhou.aliyuncs.com/your_namespace/langgenius-dify-api:1.3.0 # 创建一个指向本地 ACR 镜像的原始名称 Tagdocker tag registry.cn-hangzhou.aliyuncs.com/your_namespace/langgenius-dify-api:1.3.0 langgenius/dify-api:1.3.0 # 现在你就可以像往常一样使用原始名称了docker run --rm langgenius/dify-api:1.3.0 ...
通过这种方式,你可以最大程度地减少对现有项目配置的修改。
8. 进阶考量与最佳实践为了使你的镜像同步方案更加健壮和高效,可以考虑以下进阶实践: 8.1 镜像标签策略- 避免过度同步
latest : 尽管 latest 标签方便,但它可能指向不同版本的镜像,在生产环境中带来不确定性。建议优先同步特定版本号的镜像(例如 nginx:1.23.0 ),以确保环境的可复现性。 - 多标签镜像处理: 如果一个镜像有多个标签(例如
ubuntu:latest 和 ubuntu:22.04 ),它们可能指向相同的底层镜像层,但为了完整性,你需要在 images.txt 中分别列出它们。
8.2 同步触发与资源消耗- 合理利用 push 和
workflow_dispatch : push 触发适合当你需要同步新镜像或更新现有镜像列表时,通过代码提交自动触发。workflow_dispatch 适合进行即时、手动的同步操作,例如紧急需要某个新镜像时。
- 关注 GitHub Actions 免费额度: GitHub Actions 提供免费额度(每月 2000 分钟的 Linux Runner)。你可以通过 GitHub 账户设置查看使用情况。如果你的同步镜像数量庞大,可能需要考虑升级 GitHub 付费计划。
8.3 安全性- GitHub Secrets 保护: 确保你的 GitHub Secrets 仅供授权的 Workflow 使用,不要在 Workflow 日志中打印这些敏感信息。
8.4 错误处理与通知- Workflow 失败通知: 配置 GitHub Actions 在 Workflow 失败时发送通知。你可以通过 GitHub 邮件通知、或集成第三方服务(如 Slack、钉钉、企业微信)来接收失败提醒,以便及时介入处理。
- 日志分析: 当同步失败时,仔细分析 GitHub Actions 的运行日志。
sync.sh 中的 set -eux 会打印详细的执行命令和错误信息,帮助你快速定位问题。
8.5 自定义镜像的构建与推送- 基于 ACR 的基础镜像: 如果你的项目 Dockerfile 中使用的基础镜像来自 Docker Hub,现在你可以将其
FROM 指令修改为从 ACR 拉取,例如 FROM registry.cn-hangzhou.aliyuncs.com/your_namespace/ubuntu:22.04 。 - 在 GitHub Actions 中构建并推送自定义镜像: 如果你的项目也需要构建自己的 Docker 镜像,并且 Dockerfile 也存在于 GitHub 仓库中,你可以利用
docker/build-push-action 直接在同一个 GitHub Actions Workflow 中构建你的自定义镜像,并推送到 Aliyun ACR。
9. 总结本文为你提供了一个强大而实用的解决方案,旨在彻底解决国内 Docker 镜像访问受限的痛点。通过巧妙地结合 GitHub Actions 的全球化执行能力和阿里云 ACR 的国内高速分发网络,我们成功地构建了一个自动化、稳定、高效的 Docker 镜像同步通道。 从现在开始,你将告别因网络问题导致的 docker pull 失败和 CI/CD 流水线中断。你的开发环境将拥有稳定、高速的镜像来源,极大地提升开发效率和部署的可靠性。 这个方案不仅适用于个人开发者,也适用于小型团队,为他们在国内构建一套可靠的 Docker 生态奠定了基础。希望本文能为你带来实际的帮助,让你能够更加专注于代码本身,享受畅快的 Docker 开发体验!
10. 常见问题 (FAQ)Q1: 这个方案是完全免费的吗? A1: GitHub Actions 和 Aliyun ACR 都提供免费额度。GitHub Actions 每月有 2000 分钟的 Linux Runner 免费额度。Aliyun ACR 个人版也有一定的免费存储空间和公网下行流量。对于个人项目和小型团队,通常在免费额度内即可满足需求。超出额度或使用高级功能才会产生少量费用。 Q2: 我可以将镜像同步到其他国内 Registry 吗?例如华为云 SWR、腾讯云 TCR? A2: 是的,核心原理是相同的。你只需要修改 config.env 中的 TARGET_REGISTRY 和 TARGET_NAMESPACE ,并在 GitHub Secrets 中配置对应 Registry 的用户名和密码(通常也是 AccessKey 或应用密码)。脚本逻辑基本无需改动,因为 Docker CLI 的操作是标准化的。 Q3: 为什么我的 GitHub Actions 仍然无法访问 Docker Hub / 其他海外源? A3: 极少数情况下,GitHub Actions Runner 所在的网络环境也可能临时性遇到问题,或者你尝试同步的源(例如一些私有的或受限的 Registry)本身在国内被严格限制,甚至连海外服务器也无法间接访问。请检查 GitHub Actions 的运行日志,通常会有明确的错误提示。 Q4: 这种镜像同步是否有法律/版权风险? A4: 通常对于公共的、开源的 Docker 镜像,进行同步和分发是允许的。但请务必查阅原始镜像的许可协议,并遵守相关的法律法规。避免同步受限、商业授权或有争议的镜像。对于企业用户,建议咨询法务部门。 Q5: 如果我想强制更新某个镜像,即使它在 ACR 中已经存在了,怎么办? A5: 当前的 sync.sh 脚本会通过 docker manifest inspect 检查目标 ACR 中是否存在相同 tag 的镜像。如果存在,就会跳过。如果你想强制更新,有以下几种方式: 1. 手动从 ACR 中删除旧镜像: 然后再提交 images.txt ,脚本就会重新同步。 2. 修改 sync.sh 脚本: 删除 docker manifest inspect 的判重逻辑,或者添加一个参数来控制是否强制更新。但请注意,强制更新会增加流量消耗。 3. 使用不同的 Tag: 如果原始镜像有新内容但保持相同 Tag,可以考虑同步时使用新的 Tag,例如 myimage:latest 和 myimage:latest-new 。 |