Skip to main content
Glama
publish-container.yml17.3 kB
# ============================================================================ # GitHub Actions Workflow: 自动发布容器镜像到 GHCR # ============================================================================ # # @description 自动构建多架构 Docker 镜像并发布到 GitHub Container Registry # @author DevOps Team # @created 2025-12-30 # @updated 2025-12-30 # # @triggers # - push: # tags: ['v*.*.*'] # 语义化版本标签触发生产发布 # - workflow_dispatch: # 手动触发(用于紧急修复) # - schedule: # 定时构建(每周日重建基础镜像) # # @outputs # - 镜像地址: ghcr.io/${{ github.repository }}:latest # - 支持架构: linux/amd64, linux/arm64 # - 附加产物: SBOM, 漏洞扫描报告 # # @permissions # - contents: read # 读取仓库代码 # - packages: write # 推送到 GHCR # - id-token: write # 用于 OIDC 认证(可选) # # @dependencies # - docker/setup-buildx-action@v3 # - docker/login-action@v3 # - docker/metadata-action@v5 # - docker/build-push-action@v5 # # ============================================================================ name: "🚀 Publish Container to GHCR" on: push: tags: - "v*.*.*" workflow_dispatch: inputs: release_version: description: "手动指定发布版本(可选,示例:v1.2.3)" required: false type: string push_latest: description: "是否同时推送 latest 标签" default: true required: false type: boolean enable_cosign: description: "启用 Cosign 镜像签名(需要 OIDC 或密钥)" default: false required: false type: boolean schedule: - cron: "0 3 * * 0" # 每周日 03:00 UTC 重建基础镜像 concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: false # 生产发布不强制取消,避免发布中断 permissions: contents: read packages: write id-token: write env: REGISTRY: ghcr.io BUILD_PLATFORMS: "linux/amd64,linux/arm64" CACHE_REGISTRY: "ghcr.io/${{ github.repository }}:buildcache" DOCKERFILE: "Dockerfile.example" BUILDX_INSTANCE: "gha-multiarch-builder" SBOM_PATH: "artifacts/sbom.json" MANIFEST_REPORT: "artifacts/manifest.txt" SMOKE_REPORT: "artifacts/smoke-tests.txt" VALIDATION_TAG: "ghcr.io/${{ github.repository }}:${{ github.sha }}" ENABLE_COSIGN_SIGNING: "${{ inputs.enable_cosign || 'false' }}" PUSH_LATEST: "${{ inputs.push_latest || 'true' }}" jobs: build-and-push: name: "📦 Multi-Arch Build & Publish" runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: "📥 Checkout repository" # ---------------------------------------------------------------- # 功能说明:获取代码仓库内容,确保包含全部历史以便 metadata 解析标签 # 输入:GITHUB_TOKEN(自动提供) # 输出:工作目录代码 # 错误处理:若检出失败,后续步骤无法执行 # ---------------------------------------------------------------- uses: actions/checkout@v4.1.7 with: fetch-depth: 0 - name: "🧭 Determine release ref" # ---------------------------------------------------------------- # 功能说明:统一发布引用,手动触发时允许覆盖版本号 # 输出:环境变量 RESOLVED_REF/RESOLVED_VERSION # 错误处理:非法版本格式会中止发布 # ---------------------------------------------------------------- id: release_ref run: | set -eo pipefail SEMVER_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$' REF_NAME="${GITHUB_REF_NAME}" if [ -n "${{ inputs.release_version || '' }}" ]; then REF_NAME="${{ inputs.release_version }}" fi if ! echo "${REF_NAME}" | grep -Eq "${SEMVER_REGEX}"; then echo "::warning::未提供有效语义化版本,使用当前引用 ${REF_NAME}" fi echo "resolved_ref=${REF_NAME}" >> "$GITHUB_OUTPUT" - name: "🔧 Resolve latest tag gate" # ---------------------------------------------------------------- # 功能说明:集中计算 latest 标签是否应启用,便于审计与维护 # ---------------------------------------------------------------- id: tag_gate run: | set -euo pipefail enable="false" if [[ "${GITHUB_EVENT_NAME}" == "push" && "${GITHUB_REF}" == refs/tags/* ]]; then enable="true" elif [[ "${GITHUB_EVENT_NAME}" == "schedule" ]]; then enable="true" elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then case "${{ inputs.push_latest || 'true' }}" in true|TRUE|1|"") enable="true" ;; *) enable="false" ;; esac fi echo "enable_latest=${enable}" >> "$GITHUB_OUTPUT" - name: "🛠️ Setup QEMU for emulation" # ---------------------------------------------------------------- # 功能说明:安装 QEMU 以支持跨架构构建(arm64) # 输入:无需额外输入 # 输出:可用的 QEMU 环境 # 错误处理:安装失败将阻断后续多架构构建 # ---------------------------------------------------------------- uses: docker/setup-qemu-action@v3.2.0 with: image: tonistiigi/binfmt:latest platforms: all - name: "🏗️ Setup Docker Buildx" # ---------------------------------------------------------------- # 功能说明:创建隔离的 buildx builder,启用多架构与缓存 # 输入:BUILDX_INSTANCE # 输出:可用的 buildx builder # 错误处理:创建失败后续构建无法进行 # ---------------------------------------------------------------- id: buildx uses: docker/setup-buildx-action@v3.7.1 with: install: true driver: docker-container buildkitd-flags: "--debug" use: true name: "${{ env.BUILDX_INSTANCE }}" - name: "🗄️ Restore buildx cache" # ---------------------------------------------------------------- # 功能说明:恢复 GHA 层缓存以缩短构建时间 # 输入:cache key 绑定 workflow + ref # 输出:本地缓存目录 # 错误处理:缓存缺失仅影响性能不影响功能 # ---------------------------------------------------------------- uses: actions/cache@v4.0.2 with: path: /tmp/.buildx-cache key: buildx-${{ github.workflow }}-${{ github.ref_name }}-${{ hashFiles(env.DOCKERFILE, '**/pyproject.toml') }} restore-keys: | buildx-${{ github.workflow }}-${{ github.ref_name }}- buildx-${{ github.workflow }}- - name: "🔑 Login to GHCR" # ---------------------------------------------------------------- # 功能说明:使用 GitHub Token 登录 GHCR # 输入:GITHUB_TOKEN(packages:write 权限) # 输出:已验证的 Docker login session # 错误处理:认证失败将阻止推送镜像 # ---------------------------------------------------------------- uses: docker/login-action@v3.3.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: "📦 Extract metadata (tags, labels)" # ---------------------------------------------------------------- # 功能说明:从 Git 引用中提取语义化版本并生成 OCI 标签 # # 输入: # - github.ref: refs/tags/v1.2.3 # 输出: # - tags: latest, v1.2.3, v1.2, v1, commit SHA # - labels: org.opencontainers.image.* # # 错误处理: # - 非法标签格式将回退到 commit SHA # ---------------------------------------------------------------- id: meta uses: docker/metadata-action@v5.5.1 with: images: | ghcr.io/${{ github.repository }} tags: | type=raw,value=latest,enable=${{ steps.tag_gate.outputs.enable_latest == 'true' }} type=raw,value=${{ github.sha }} type=semver,pattern={{version}},prefix=v type=semver,pattern={{major}}.{{minor}},prefix=v type=semver,pattern={{major}},prefix=v type=sha,format=long labels: | org.opencontainers.image.title=${{ github.repository }} org.opencontainers.image.description=Pandoc MCP container image with multi-arch support org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.licenses=MIT - name: "🧮 Render tag summary" # ---------------------------------------------------------------- # 功能说明:列出实际将要推送的标签,便于审计 # 输出:human readable summary # ---------------------------------------------------------------- run: | echo "Tags to be published:" printf '%s\n' "${{ steps.meta.outputs.tags }}" - name: "🔐 Optional: Install Cosign" # ---------------------------------------------------------------- # 功能说明:在需要签名时安装 cosign(使用 OIDC 或密钥) # 条件:ENABLE_COSIGN_SIGNING == 'true' # ---------------------------------------------------------------- if: env.ENABLE_COSIGN_SIGNING == 'true' uses: sigstore/cosign-installer@v3.5.0 with: cosign-release: "v2.4.0" - name: "🏗️ Build & Push multi-arch image" # ---------------------------------------------------------------- # 功能说明:使用 buildx 构建并推送多架构镜像,启用三层缓存 # 缓存: # - Registry cache: ${{ env.CACHE_REGISTRY }} # - GitHub Actions cache: actions/cache 目录 # - 本地 buildx cache: /tmp/.buildx-cache # ---------------------------------------------------------------- id: build_push uses: docker/build-push-action@v5.3.0 with: context: . file: ${{ env.DOCKERFILE }} pull: true push: true provenance: false sbom: true platforms: ${{ env.BUILD_PLATFORMS }} builder: ${{ steps.buildx.outputs.name }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: | type=gha,scope=${{ github.workflow }}-${{ github.ref_name }} type=registry,ref=${{ env.CACHE_REGISTRY }} type=local,src=/tmp/.buildx-cache cache-to: | type=gha,mode=max,scope=${{ github.workflow }}-${{ github.ref_name }} type=registry,ref=${{ env.CACHE_REGISTRY }},mode=max type=local,dest=/tmp/.buildx-cache-new,mode=max outputs: type=image,push=true - name: "🔄 Move local cache" # ---------------------------------------------------------------- # 功能说明:将新缓存目录覆盖旧目录,保持缓存可复用 # ---------------------------------------------------------------- run: | rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache - name: "🛡️ Cosign sign image" # ---------------------------------------------------------------- # 功能说明:当启用时对镜像进行签名(示例使用 OIDC) # 条件:ENABLE_COSIGN_SIGNING == 'true' # ---------------------------------------------------------------- if: env.ENABLE_COSIGN_SIGNING == 'true' env: COSIGN_EXPERIMENTAL: "1" run: | set -euo pipefail TARGET_IMAGE="ghcr.io/${{ github.repository }}@${{ steps.build_push.outputs.digest }}" cosign sign --yes "${TARGET_IMAGE}" - name: "🧾 Inspect multi-arch manifest" # ---------------------------------------------------------------- # 功能说明:验证多架构 manifest 是否包含期望架构 # 输出:manifest 报告写入 $MANIFEST_REPORT # ---------------------------------------------------------------- run: | mkdir -p "$(dirname "${MANIFEST_REPORT}")" docker buildx imagetools inspect "${VALIDATION_TAG}" | tee "${MANIFEST_REPORT}" - name: "🚦 Smoke test (linux/amd64)" # ---------------------------------------------------------------- # 功能说明:拉取并运行 amd64 镜像进行冒烟测试 # ---------------------------------------------------------------- run: | docker pull --platform linux/amd64 "${VALIDATION_TAG}" docker run --rm --platform linux/amd64 "${VALIDATION_TAG}" --help | head -n 20 | tee -a "${SMOKE_REPORT}" - name: "🚦 Smoke test (linux/arm64 via QEMU)" # ---------------------------------------------------------------- # 功能说明:拉取并运行 arm64 镜像进行冒烟测试(依赖 QEMU) # ---------------------------------------------------------------- run: | docker pull --platform linux/arm64 "${VALIDATION_TAG}" docker run --rm --platform linux/arm64 "${VALIDATION_TAG}" --help | head -n 20 | tee -a "${SMOKE_REPORT}" - name: "📤 Upload reports" # ---------------------------------------------------------------- # 功能说明:上传 manifest 与 smoke test 报告用于审计 # ---------------------------------------------------------------- uses: actions/upload-artifact@v4.4.0 with: name: publish-reports path: | ${{ env.MANIFEST_REPORT }} ${{ env.SMOKE_REPORT }} - name: "🧹 Cleanup builder" # ---------------------------------------------------------------- # 功能说明:清理构建器与悬挂资源,避免磁盘泄露 # ---------------------------------------------------------------- if: always() run: | docker buildx rm "${{ steps.buildx.outputs.name }}" || true - name: "❗ Handle build failure" # ---------------------------------------------------------------- # 功能说明:统一处理失败,输出错误并发送通知 # ---------------------------------------------------------------- if: failure() env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} EMAIL_SMTP_ENDPOINT: ${{ secrets.EMAIL_SMTP_ENDPOINT }} EMAIL_TO: ${{ secrets.EMAIL_TO }} NOTIFY_FROM_EMAIL: ${{ secrets.NOTIFY_FROM_EMAIL || 'github-actions@users.noreply.github.com' }} SMTP_USERNAME: ${{ secrets.EMAIL_SMTP_USERNAME }} SMTP_PASSWORD: ${{ secrets.EMAIL_SMTP_PASSWORD }} run: | echo "::error::构建失败详情,检查上方日志。" docker buildx inspect "${{ steps.buildx.outputs.name }}" >/tmp/buildx-inspect.log 2>&1 || true docker info >/tmp/docker-info.log 2>&1 || true echo "Buildx inspect (last 120 lines):" tail -n 120 /tmp/buildx-inspect.log || true echo "Docker info (last 120 lines):" tail -n 120 /tmp/docker-info.log || true if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then payload=$(jq -n --arg text "GHCR 发布失败:${GITHUB_REPOSITORY} @ ${GITHUB_REF}. 请检查日志。" '{text:$text}') curl -X POST -H "Content-Type: application/json" -d "${payload}" "${SLACK_WEBHOOK_URL}" fi if [ -n "${EMAIL_SMTP_ENDPOINT:-}" ] && [ -n "${EMAIL_TO:-}" ]; then python - <<'PY' import os, smtplib, ssl, sys from email.message import EmailMessage msg = EmailMessage() msg["Subject"] = "GHCR 发布失败告警" msg["From"] = os.environ.get("NOTIFY_FROM_EMAIL", "github-actions@users.noreply.github.com") msg["To"] = os.environ["EMAIL_TO"] msg.set_content(f"仓库 {os.environ['GITHUB_REPOSITORY']} 发布失败,参考运行 {os.environ['GITHUB_RUN_ID']}") context = ssl.create_default_context() try: with smtplib.SMTP(os.environ["EMAIL_SMTP_ENDPOINT"]) as smtp: smtp.starttls(context=context) user = os.environ.get("SMTP_USERNAME") pwd = os.environ.get("SMTP_PASSWORD") if user and pwd: smtp.login(user, pwd) smtp.send_message(msg) except Exception as exc: print(f"::warning::邮件发送失败: {exc}", file=sys.stderr) PY fi

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Fu-Jie/MCP-OPENAPI-Pandoc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server