@@ -28,10 +28,12 @@ permissions:
2828 id-token : write
2929
3030jobs :
31- docker :
32- runs-on : ubuntu-latest
31+ build :
3332 strategy :
33+ fail-fast : false
3434 matrix :
35+ image : [server, agent, web, mcp]
36+ platform : [linux/amd64, linux/arm64]
3537 include :
3638 - image : server
3739 dockerfile : docker/Dockerfile.server
4143 dockerfile : docker/Dockerfile.web
4244 - image : mcp
4345 dockerfile : docker/Dockerfile.mcp
46+ - platform : linux/amd64
47+ runner : ubuntu-latest
48+ - platform : linux/arm64
49+ runner : ubuntu-24.04-arm64
50+ runs-on : ${{ matrix.runner }}
4451 steps :
4552 - name : Checkout
4653 uses : actions/checkout@v4
@@ -52,26 +59,23 @@ jobs:
5259 images : |
5360 memohai/${{ matrix.image }}
5461 ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
55- tags : |
56- type=raw,value=dev,enable=${{ github.ref == 'refs/heads/main' }}
57- type=raw,value=latest,enable=${{ github.event_name == 'release' || (startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-')) }}
58- type=ref,event=pr
59- type=semver,pattern={{version}}
60- type=semver,pattern={{major}}.{{minor}}
61- type=semver,pattern={{major}}
62- type=sha
63- labels : |
64- org.opencontainers.image.title=memoh-${{ matrix.image }}
65- org.opencontainers.image.description=Memoh ${{ matrix.image }} - Multi-member AI agent platform
66- org.opencontainers.image.vendor=memohai
67-
68- - name : Set up QEMU
69- if : startsWith(github.ref, 'refs/tags/')
70- uses : docker/setup-qemu-action@v3
7162
7263 - name : Set up Docker Buildx
7364 uses : docker/setup-buildx-action@v3
7465
66+ - name : Set up Go
67+ if : matrix.image == 'server' || matrix.image == 'mcp'
68+ uses : actions/setup-go@v5
69+ with :
70+ go-version : ' 1.25'
71+ cache : true
72+
73+ - name : Pre-warm Go mod cache
74+ if : matrix.image == 'server' || matrix.image == 'mcp'
75+ run : |
76+ mkdir -p .go-cache
77+ GOMODCACHE=$(pwd)/.go-cache go mod download
78+
7579 - name : Login to Docker Hub
7680 if : github.event_name != 'pull_request'
7781 uses : docker/login-action@v3
@@ -87,22 +91,97 @@ jobs:
8791 username : ${{ github.actor }}
8892 password : ${{ secrets.GITHUB_TOKEN }}
8993
90- - name : Build and push
94+ - name : Build and push by digest
95+ id : build
9196 uses : docker/build-push-action@v6
9297 with :
9398 context : .
9499 file : ${{ matrix.dockerfile }}
95- push : ${{ github.event_name != 'pull_request' }}
96- tags : ${{ steps.meta.outputs.tags }}
97- labels : ${{ steps.meta.outputs.labels }}
98- platforms : ${{ startsWith(github.ref, 'refs/tags/') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
100+ platforms : ${{ matrix.platform }}
101+ # 仅在 main 分支、tag 推送或 release 发布时才真正推送
102+ push : ${{ github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') || github.event_name == 'release') }}
103+ outputs : ${{ (github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') || github.event_name == 'release')) && 'type=image,name-canonical=true,push-by-digest=true' || 'type=image,push=false' }}
104+ build-contexts : |
105+ gomodcache=${{ github.workspace }}/.go-cache
99106 build-args : |
100107 VERSION=${{ steps.meta.outputs.version }}
101108 COMMIT_HASH=${{ github.sha }}
102- BUILD_TIME=${{ fromJson(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
103109 VITE_API_URL=/api
104110 VITE_AGENT_URL=/agent
105- provenance : ${{ startsWith(github.ref, 'refs/tags/') }}
106- sbom : ${{ startsWith(github.ref, 'refs/tags/') }}
107- cache-from : type=gha,scope=${{ matrix.image }}
108- cache-to : type=gha,scope=${{ matrix.image }},mode=max
111+ cache-from : type=gha,scope=${{ matrix.image }}-${{ matrix.platform }}
112+ cache-to : type=gha,scope=${{ matrix.image }}-${{ matrix.platform }},mode=max
113+
114+ - name : Export digest
115+ if : github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') || github.event_name == 'release')
116+ run : |
117+ mkdir -p /tmp/digests
118+ digest="${{ steps.build.outputs.digest }}"
119+ touch "/tmp/digests/${digest#sha256:}"
120+
121+ - name : Upload digest
122+ if : github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') || github.event_name == 'release')
123+ uses : actions/upload-artifact@v4
124+ with :
125+ name : digests-${{ matrix.image }}-${{ strategy.job-index }}
126+ path : /tmp/digests/*
127+ if-no-files-found : error
128+ retention-days : 1
129+
130+ merge :
131+ runs-on : ubuntu-latest
132+ if : github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') || github.event_name == 'release')
133+ needs : build
134+ strategy :
135+ matrix :
136+ image : [server, agent, web, mcp]
137+ steps :
138+ - name : Download digests
139+ uses : actions/download-artifact@v4
140+ with :
141+ path : /tmp/digests
142+ pattern : digests-${{ matrix.image }}-*
143+ merge-multiple : true
144+
145+ - name : Set up Docker Buildx
146+ uses : docker/setup-buildx-action@v3
147+
148+ - name : Login to Docker Hub
149+ uses : docker/login-action@v3
150+ with :
151+ username : ${{ secrets.DOCKERHUB_USERNAME }}
152+ password : ${{ secrets.DOCKERHUB_TOKEN }}
153+
154+ - name : Login to GitHub Container Registry
155+ uses : docker/login-action@v3
156+ with :
157+ registry : ghcr.io
158+ username : ${{ github.actor }}
159+ password : ${{ secrets.GITHUB_TOKEN }}
160+
161+ - name : Docker meta
162+ id : meta
163+ uses : docker/metadata-action@v5
164+ with :
165+ images : |
166+ memohai/${{ matrix.image }}
167+ ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
168+ tags : |
169+ type=raw,value=dev,enable=${{ github.ref == 'refs/heads/main' }}
170+ type=raw,value=latest,enable=${{ github.event_name == 'release' || (startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-')) }}
171+ type=ref,event=pr
172+ type=semver,pattern={{version}}
173+ type=semver,pattern={{major}}.{{minor}}
174+ type=semver,pattern={{major}}
175+
176+ - name : Create manifest list and push
177+ working-directory : /tmp/digests
178+ run : |
179+ # 提取所有标签并格式化为 -t tag1 -t tag2
180+ TAG_ARGS=$(echo "${{ steps.meta.outputs.tags }}" | xargs -I {} echo "-t {}")
181+
182+ # 使用 Docker Hub 的引用作为源来合并 manifest (因为 build 阶段已经推送到所有仓库)
183+ # printf 会遍历当前目录下所有的 digest 文件名
184+ SOURCES=$(printf "memohai/${{ matrix.image }}@sha256:%s " *)
185+
186+ echo "Creating manifest for ${{ matrix.image }} with tags: $TAG_ARGS"
187+ docker buildx imagetools create $TAG_ARGS $SOURCES
0 commit comments