@@ -34,33 +34,43 @@ 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 single-job multi-platform builds with `docker/build-push-action@v5`
38- - Build for both linux/amd64 and linux/arm64 in one step
37+ - Use **platform matrix** for parallel builds of linux/amd64 and linux/arm64
38+ - Build job creates platform-specific images (2 parallel jobs per version)
39+ - Manifest job combines them into multi-arch manifest
3940- Images are **only pushed to GHCR from master branch**
4041- Dev and PR branches build only (no push)
4142
43+ **Why platform matrix:**
44+ - ⚡ **Faster**: Both platforms build in parallel (~5-10 min vs 40-60 min sequential)
45+ - 🎯 **Better debugging**: Platform-specific failures don't block others
46+ - 🔍 **Clear visibility**: See exactly which platform succeeded/failed
47+ - 💾 **Platform-specific caching**: Each platform has its own cache scope
48+
4249### Job Naming Convention
4350
44- **Use image-first naming pattern:**
45- - Format: `<image-name>-build`
46- - Examples: `esp-idf-build`, `platformio-build`, `esp-matter-build `
51+ **Use image-first naming pattern with two jobs :**
52+ - Format: `<image-name>-build` and `<image-name>-manifest`
53+ - Examples: `esp-idf-build`/`esp-idf-manifest` , `platformio-build`/`platformio-manifest `
4754
4855**Benefits:**
49- - Clear and simple naming
50- - Each image has one job that handles multi-platform builds
56+ - True parallel builds (both platforms simultaneously)
57+ - Better failure isolation (one platform can fail without blocking other)
58+ - Platform-specific caching
5159- Jobs group together alphabetically in GitHub UI by image name
5260- Scalable - easy to add more images without conflicts
5361
5462**Example workflow structure:**
5563```yaml
5664jobs:
57- esp-idf-build: # Builds multi-platform ESP-IDF image
58- platformio-build: # Builds multi-platform PlatformIO image
65+ esp-idf-build: # Builds platform-specific ESP-IDF images (amd64, arm64)
66+ esp-idf-manifest: # Creates multi-arch manifest from platform images
67+ platformio-build: # Builds platform-specific PlatformIO images (amd64, arm64)
68+ platformio-manifest: # Creates multi-arch manifest from platform images
5969```
6070
6171### Workflow Structure
6272
63- Standard pattern for image workflows with multi- platform builds:
73+ Standard pattern for image workflows with platform matrix builds:
6474
6575```yaml
6676name: 🐳 <Image Name> Docker Image
@@ -70,7 +80,7 @@ name: 🐳 <Image Name> Docker Image
7080# - Main repo dev/PR: Build only (validation)
7181# - workflow_dispatch: Build on any branch/fork, push only on main repo master
7282# - Forks: Can test builds, but push is restricted to main repo
73- # - Multi-platform: Builds for linux/amd64 and linux/arm64 in single job
83+ # - Multi-platform: Platform matrix builds linux/amd64 and linux/arm64 in parallel
7484
7585on:
7686 push:
91101
92102jobs:
93103 <image-name>-build:
94- runs-on: ubuntu-latest
104+ runs-on: ubuntu-latest # or [self-hosted, ubuntu-latest] for large builds
95105 timeout-minutes: 60
96106 if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'
97107 permissions:
@@ -101,6 +111,7 @@ jobs:
101111 fail-fast: false
102112 matrix:
103113 version: ['1.0.0'] # Can expand to multiple versions
114+ platform: ['linux/amd64', 'linux/arm64']
104115 steps:
105116 - name: 📥 Checkout repository
106117 uses: actions/checkout@v4
@@ -121,33 +132,70 @@ jobs:
121132 run: |
122133 SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
123134 echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
135+ PLATFORM_TAG=$(echo "${{ matrix.platform }}" | tr '/' '-')
136+ echo "platform_tag=${PLATFORM_TAG}" >> $GITHUB_OUTPUT
124137
125- - name: 🐳 Build and push multi- platform image
138+ - name: 🐳 Build and push platform-specific image
126139 uses: docker/build-push-action@v5
127140 with:
128141 context: images/<image-name>
129- platforms: linux/amd64,linux/arm64
142+ platforms: ${{ matrix.platform }}
130143 push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
131144 tags: |
132- ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:latest
133- ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:sha-${{ steps.tags.outputs.sha_short }}
134- ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}
135- cache-from: type=gha,scope=${{ env.<IMAGE_NAME> }}
136- cache-to: type=gha,mode=max,scope=${{ env.<IMAGE_NAME> }}
145+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}-${{ steps.tags.outputs.platform_tag }}
146+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:sha-${{ steps.tags.outputs.sha_short }}-${{ steps.tags.outputs.platform_tag }}
147+ cache-from: type=gha,scope=${{ env.<IMAGE_NAME> }}-${{ steps.tags.outputs.platform_tag }}
148+ cache-to: type=gha,mode=max,scope=${{ env.<IMAGE_NAME> }}-${{ steps.tags.outputs.platform_tag }}
137149 build-args: |
138150 VERSION=${{ matrix.version }}
151+
152+ <image-name>-manifest:
153+ runs-on: ubuntu-latest
154+ timeout-minutes: 10
155+ needs: <image-name>-build
156+ if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'
157+ permissions:
158+ contents: read
159+ packages: write
160+ strategy:
161+ fail-fast: false
162+ matrix:
163+ version: ['1.0.0']
164+ steps:
165+ - name: 🔐 Log in to GitHub Container Registry
166+ uses: docker/login-action@v3
167+ with:
168+ registry: ${{ env.REGISTRY }}
169+ username: ${{ github.actor }}
170+ password: ${{ secrets.GITHUB_TOKEN }}
171+
172+ - name: 🏷️ Generate tags
173+ id: tags
174+ run: |
175+ SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
176+ echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
177+
178+ - name: 🐳 Create and push multi-arch manifest
179+ run: |
180+ docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:latest \
181+ -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:sha-${{ steps.tags.outputs.sha_short }} \
182+ -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }} \
183+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}-linux-amd64 \
184+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}-linux-arm64
139185```
140186
141187**Critical Details:**
142- - **Single-job approach**: One job builds multi-arch image using `docker/build-push-action@v5`
143- - **Multi-platform builds**: `platforms: linux/amd64,linux/arm64` builds both architectures simultaneously
144- - **All tags created together**: All tags (latest, stable, version, SHA) created in single build step
145- - **Job naming**: Use `<image-name>-build` pattern (e.g., `esp-idf-build`, `platformio-build`)
188+ - **Platform matrix approach**: Build job uses `platform: ['linux/amd64', 'linux/arm64']` in matrix
189+ - **Parallel builds**: Both platforms build simultaneously (faster, better failure isolation)
190+ - **Platform-specific tags**: Build job creates tags with platform suffix (e.g., `-linux-amd64`)
191+ - **Manifest job**: Separate job combines platform images into multi-arch manifest
192+ - **Final tags**: Manifest job creates `latest`, `sha-XXX`, and version tags
193+ - **Job naming**: Use `<image-name>-build` and `<image-name>-manifest` pattern
146194- **Image name variables**: Use descriptive uppercase names like `ESP_IDF_IMAGE_NAME`, not generic `IMAGE_NAME`
147- - **Simplified cache scoping**: Single cache scope `scope=${{ env.<IMAGE_NAME> }}` for all platforms
148- - **Timeout**: Build jobs use `timeout-minutes: 60`
195+ - **Platform-specific caching**: Cache scope includes platform tag: `scope=${{ env.<IMAGE_NAME> }}-${{ steps.tags.outputs.platform_tag }}`
196+ - **Platform tag generation**: `PLATFORM_TAG=$(echo "${{ matrix.platform }}" | tr '/' '-')` converts `linux/amd64` to `linux-amd64`
197+ - **Timeout**: Build jobs use `timeout-minutes: 60`, manifest jobs use `timeout-minutes: 10`
149198- **Fail-fast**: Use `fail-fast: false` to prevent matrix cancellation on first failure
150- - **No separate manifest job needed**: `docker/build-push-action` creates multi-arch manifest automatically
151199- Only master branch pushes images; dev/PR do build-only validation
152200- Version matrix can be expanded to build multiple versions
153201- Simple `workflow_dispatch` trigger allows manual runs from any branch/fork
0 commit comments