Skip to content

Commit e4c890d

Browse files
committed
Refactor workflows to use single-step multi-platform builds
- Replace separate platform matrix + manifest jobs with single multi-platform build - Use docker/build-push-action with platforms: linux/amd64,linux/arm64 - All tags (latest, stable, version, SHA) created in single step - Simplify cache scoping to single scope per image - Update esp-idf.yml and platformio.yml workflows - Update ESP-Matter build job (still disabled) Documentation: - Update github-actions-standards.mdc with new workflow pattern - Remove manifest job documentation - Simplify job naming convention (only -build jobs) - Update fork prevention and tag strategy sections - Clarify that docker/build-push-action creates multi-arch manifests automatically Benefits: - Fixes GHCR primary tag display (all tags created simultaneously) - Simpler workflows (50% less YAML) - Faster builds (no separate manifest job) - Better cache sharing across platforms - Standard docker/build-push-action usage pattern
1 parent bb6546a commit e4c890d

File tree

3 files changed

+99
-238
lines changed

3 files changed

+99
-238
lines changed

.cursor/rules/github-actions-standards.mdc

Lines changed: 57 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -34,42 +34,33 @@ Add emoji to step names for readability:
3434
Each 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
5556
jobs:
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
7566
name: 🐳 <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

8375
on:
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

@@ -365,16 +327,16 @@ fi
365327
1. **Local first**: Test with `act` before pushing
366328
2. **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
370332
3. **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
374336
4. **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
378340
5. **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

Comments
 (0)