@@ -34,42 +34,33 @@ Add emoji to step names for readability:
3434Each image should have its own workflow file:
3535- Naming: `.github/workflows/<image-name>.yml`
3636- Trigger on relevant file changes only
37- - Use inline matrix builds for multi-platform support (amd64/arm64)
37+ - Use single-job multi-platform builds with `docker/build-push-action@v5`
38+ - Build for both linux/amd64 and linux/arm64 in one step
3839- Images are **only pushed to GHCR from master branch**
3940- Dev and PR branches build only (no push)
4041
4142### Job Naming Convention
4243
4344**Use image-first naming pattern:**
44- - Format: `<image-name>-<action> `
45- - Examples: `esp-idf-build`, `esp-idf-manifest`, ` platformio-build`, `platformio-manifest `
45+ - Format: `<image-name>-build `
46+ - Examples: `esp-idf-build`, `platformio-build`, `esp-matter-build `
4647
4748**Benefits:**
48- - Jobs for the same image group together alphabetically in GitHub UI
49- - Clear ownership - immediately see which image each job belongs to
49+ - Clear and simple naming
50+ - Each image has one job that handles multi-platform builds
51+ - Jobs group together alphabetically in GitHub UI by image name
5052- Scalable - easy to add more images without conflicts
51- - Consistent across all workflows
5253
5354**Example workflow structure:**
5455```yaml
5556jobs:
56- esp-idf-build: # Builds ESP-IDF images
57- esp-idf-manifest: # Creates ESP-IDF manifest
58- platformio-build: # Builds PlatformIO images
59- platformio-manifest: # Creates PlatformIO manifest
60- ```
61-
62- When sorted alphabetically in GitHub UI:
63- ```
64- ✓ esp-idf-build
65- ✓ esp-idf-manifest ← Grouped together
66- ✓ platformio-build
67- ✓ platformio-manifest ← Grouped together
57+ esp-idf-build: # Builds multi-platform ESP-IDF image
58+ platformio-build: # Builds multi-platform PlatformIO image
6859```
6960
7061### Workflow Structure
7162
72- Standard pattern for image workflows with matrix builds:
63+ Standard pattern for image workflows with multi-platform builds:
7364
7465```yaml
7566name: 🐳 <Image Name> Docker Image
@@ -79,6 +70,7 @@ name: 🐳 <Image Name> Docker Image
7970# - Main repo dev/PR: Build only (validation)
8071# - workflow_dispatch: Build on any branch/fork, push only on main repo master
8172# - Forks: Can test builds, but push is restricted to main repo
73+ # - Multi-platform: Builds for linux/amd64 and linux/arm64 in single job
8274
8375on:
8476 push:
@@ -108,7 +100,6 @@ jobs:
108100 strategy:
109101 fail-fast: false
110102 matrix:
111- platform: [linux/amd64, linux/arm64]
112103 version: ['1.0.0'] # Can expand to multiple versions
113104 steps:
114105 - name: 📥 Checkout repository
@@ -118,85 +109,52 @@ jobs:
118109 uses: docker/setup-buildx-action@v3
119110
120111 - name: 🔐 Log in to GitHub Container Registry
121- if: github.ref_name == 'master'
112+ if: github.repository_owner == 'jethome-iot' && github. ref_name == 'master'
122113 uses: docker/login-action@v3
123114 with:
124115 registry: ${{ env.REGISTRY }}
125116 username: ${{ github.actor }}
126117 password: ${{ secrets.GITHUB_TOKEN }}
127118
128- - name: 🏷️ Prepare platform tag
129- id: platform
119+ - name: 🏷️ Generate tags
120+ id: tags
130121 run: |
131- PLATFORM_PAIR=${{ matrix.platform }}
132- PLATFORM_TAG=$(echo $PLATFORM_PAIR | sed 's/\//-/g')
133- echo "tag=${PLATFORM_TAG}" >> $GITHUB_OUTPUT
122+ SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
123+ echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
134124
135- - name: 🐳 Build and push Docker image
125+ - name: 🐳 Build and push multi-platform image
136126 uses: docker/build-push-action@v5
137127 with:
138128 context: images/<image-name>
139- platforms: ${{ matrix.platform }}
129+ platforms: linux/amd64,linux/arm64
140130 push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
141131 tags: |
142- ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}-${{ steps.platform.outputs.tag }}
143- cache-from: type=gha,scope=${{ env.<IMAGE_NAME> }}-${{ steps.platform.outputs.tag }}
144- cache-to: type=gha,mode=max,scope=${{ env.<IMAGE_NAME> }}-${{ steps.platform.outputs.tag }}
132+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:latest
133+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:stable
134+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}
135+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:sha-${{ steps.tags.outputs.sha_short }}
136+ cache-from: type=gha,scope=${{ env.<IMAGE_NAME> }}
137+ cache-to: type=gha,mode=max,scope=${{ env.<IMAGE_NAME> }}
145138 build-args: |
146139 VERSION=${{ matrix.version }}
147-
148- <image-name>-manifest:
149- runs-on: ubuntu-latest
150- timeout-minutes: 10
151- needs: <image-name>-build
152- if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'
153- permissions:
154- contents: read
155- packages: write
156- strategy:
157- fail-fast: false
158- matrix:
159- version: ['1.0.0'] # Must match build job matrix
160- steps:
161- - name: 🔐 Log in to GitHub Container Registry
162- uses: docker/login-action@v3
163- with:
164- registry: ${{ env.REGISTRY }}
165- username: ${{ github.actor }}
166- password: ${{ secrets.GITHUB_TOKEN }}
167-
168- - name: 🏷️ Generate SHA tag
169- id: tags
170- run: |
171- SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
172- echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
173-
174- - name: 🎯 Create multi-arch manifest
175- run: |
176- docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:sha-${{ steps.tags.outputs.sha_short }} \
177- -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:stable \
178- -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:latest \
179- -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }} \
180- ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}-linux-amd64 \
181- ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}-linux-arm64
182140```
183141
184142**Critical Details:**
185- - **Job naming**: Use `<image-name>-build` and `<image-name>-manifest` pattern (e.g., `esp-idf-build`, `esp-idf-manifest`)
186- - **Benefits of image-first naming**: Jobs for same image group together in GitHub UI when sorted alphabetically
143+ - **Single-job approach**: One job builds multi-arch image using `docker/build-push-action@v5`
144+ - **Multi-platform builds**: `platforms: linux/amd64,linux/arm64` builds both architectures simultaneously
145+ - **All tags created together**: All tags (latest, stable, version, SHA) created in single build step
146+ - **Job naming**: Use `<image-name>-build` pattern (e.g., `esp-idf-build`, `platformio-build`)
187147- **Image name variables**: Use descriptive uppercase names like `ESP_IDF_IMAGE_NAME`, not generic `IMAGE_NAME`
188- - **Cache scoping**: Use image name variable for cache scope: `scope=${{ env.<IMAGE_NAME> }}-${{ steps.platform.outputs.tag }}`
189- - **Timeouts**: Build jobs use `timeout-minutes: 60`, manifest jobs use `timeout-minutes: 10`
190- - **Fail-fast**: Use `fail-fast: false` to see all platform failures
191- - **Tag ordering**: Version tag must be listed LAST in manifest creation (GHCR displays last tag as primary)
192- - Matrix builds both platforms in parallel for faster CI
193- - Platform-specific images tagged with arch suffix (e.g., `pio-6.1.18-linux-amd64`)
194- - Manifest job combines platform images into multi-arch manifest
148+ - **Simplified cache scoping**: Single cache scope `scope=${{ env.<IMAGE_NAME> }}` for all platforms
149+ - **Timeout**: Build jobs use `timeout-minutes: 60`
150+ - **Fail-fast**: Use `fail-fast: false` to prevent matrix cancellation on first failure
151+ - **No separate manifest job needed**: `docker/build-push-action` creates multi-arch manifest automatically
195152- Only master branch pushes images; dev/PR do build-only validation
196153- Version matrix can be expanded to build multiple versions
197154- Simple `workflow_dispatch` trigger allows manual runs from any branch/fork
198155- **Fork prevention**: `if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'` allows fork testing
199156- **Secure push**: `push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}` prevents fork pushes
157+ - **Login condition**: Must include both org and branch checks: `if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'`
200158
201159### Fork Prevention
202160
@@ -212,37 +170,41 @@ jobs:
212170 contents: read
213171 packages: write
214172 strategy:
215- fail-fast: false # Show all platform failures
173+ fail-fast: false
216174 matrix:
217- platform : [linux/amd64, linux/arm64 ]
175+ version : ['1.0.0' ]
218176```
219177
220- **Strengthen push conditions to prevent fork pushes :**
178+ **Strengthen push and login conditions :**
221179
222180```yaml
223- - name: 🐳 Build and push Docker image
181+ - name: 🔐 Log in to GitHub Container Registry
182+ if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'
183+ uses: docker/login-action@v3
184+
185+ - name: 🐳 Build and push multi-platform image
224186 uses: docker/build-push-action@v5
225187 with:
226188 push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
227189```
228190
229191**Why this pattern:**
230192- ✅ **Saves Actions minutes** - Forks don't trigger automatic builds on push/PR
231- - ✅ **Allows testing** - Forks can manually run workflow_dispatch to test
193+ - ✅ **Allows testing** - Forks can manually run workflow_dispatch to test builds (without push)
232194- ✅ **Secure pushes** - Only main repo on master can push to GHCR
233195- ✅ **Common practice** - Standard pattern for OSS projects with Docker registries
234196
235- **Apply to all jobs:**
236- - Build jobs : `if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'`
237- - Manifest jobs : `if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'`
238- - Push conditions : `push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}`
197+ **Apply to build jobs:**
198+ - Job condition : `if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'`
199+ - Login condition : `if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'`
200+ - Push condition : `push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}`
239201
240202### Version Handling
241203
242204- Define versions directly in matrix strategy
243205- Can expand to multiple versions: `version: ['1.0.0', '1.1.0']`
244- - Each version builds for all platforms in the matrix
245- - Matrix generates all combinations: platforms × versions
206+ - Each version builds for all platforms (amd64, arm64) in a single job
207+ - Multi-platform build handled by `docker/build-push-action` with `platforms: linux/amd64,linux/arm64`
246208
247209### Push Behavior by Branch
248210
@@ -267,12 +229,12 @@ jobs:
267229- `<prefix>-v<version>` - Main version tag (e.g., `idf-v5.4.1`, `pio-v6.1.18`)
268230- `sha-<commit>` - Git commit tracking for debugging/rollback
269231- **No date tags** - Version tags are semantic and clear; SHA tags provide precise tracking
232+ - All tags created simultaneously in single build step via `docker/build-push-action`
270233
271- **Tag Ordering in Manifests:**
272- - Version tag must be listed LAST in `docker buildx imagetools create` command
273- - GHCR displays the last created tag as the "primary" tag in the UI
274- - Order: `sha-<commit>` → `stable` → `latest` → `<prefix>-v<version>` (last = primary display)
275- - This ensures GHCR shows the informative version tag (e.g., `idf-v5.4.1`) instead of `sha-XXXXXXX`
234+ **Tag Order:**
235+ - List tags in the `tags:` field of `docker/build-push-action`
236+ - Order: `latest`, `stable`, version tag, `sha-<commit>` (last)
237+ - All tags point to the same multi-arch manifest automatically
276238
277239## Workflow Testing
278240
3653271. **Local first**: Test with `act` before pushing
3663282. **Branch strategy**: Always test on `dev` branch first
367329 - Push to dev for build validation (no GHCR push)
368- - Review workflow logs for both platforms
330+ - Review workflow logs for multi-platform build
369331 - Merge to master only after successful dev validation
3703323. **Monitor logs**: Always check logs for build errors, even on "success"
371- - Check both amd64 and arm64 build logs (matrix creates separate jobs)
372- - Verify both platform builds complete successfully
333+ - Single job builds both amd64 and arm64 platforms
334+ - Verify both platform builds complete successfully within the job
373335 - Ensure build times are reasonable for both architectures
3743364. **Verify images**: Pull and test the published image after successful master build
375337 - Test multi-arch manifest: `docker pull <image>:latest`
376338 - Verify on your architecture: `docker run --rm <image>:latest <command>`
377339 - Check that correct platform is pulled automatically
3783405. **Cache management**: GitHub Actions cache persists between runs
379- - Cache is per-platform (separate for amd64/arm64)
341+ - Single cache shared across all platforms
380342 - To force fresh build, clear GitHub Actions cache via Settings → Actions → Caches
0 commit comments