Skip to content

Commit ba7a323

Browse files
committed
Refactor workflows to use platform matrix builds
- Change build strategy from single-job multi-platform to two-job pattern - Add separate build and manifest jobs for parallel platform builds - Build job creates platform-specific images (linux/amd64, linux/arm64) - Manifest job combines platform images into multi-arch manifest - Add platform-specific cache scoping for better cache isolation - Increase ESP-Matter build timeout to 120 minutes - Update GitHub Actions standards to document platform matrix approach
1 parent 2dfc40c commit ba7a323

File tree

3 files changed

+214
-52
lines changed

3 files changed

+214
-52
lines changed

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

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,43 @@ 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 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
5664
jobs:
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
6676
name: 🐳 <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

7585
on:
7686
push:
@@ -91,7 +101,7 @@ env:
91101

92102
jobs:
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

.github/workflows/esp-idf.yml

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# - Main repo dev/PR: Build only (validation)
77
# - workflow_dispatch: Build on any branch/fork, push only on main repo master
88
# - Forks: Can test builds, but push is restricted to main repo
9-
# - Multi-platform: Builds for linux/amd64 and linux/arm64 in single job
9+
# - Multi-platform: Platform matrix builds linux/amd64 and linux/arm64 in parallel
1010
name: 🐳 ESP-IDF Docker Image
1111

1212
on:
@@ -41,6 +41,7 @@ jobs:
4141
fail-fast: false
4242
matrix:
4343
idf_base_tag: ['v5.4.1']
44+
platform: ['linux/amd64', 'linux/arm64']
4445
steps:
4546
- name: 📥 Checkout repository
4647
uses: actions/checkout@v4
@@ -61,35 +62,73 @@ jobs:
6162
run: |
6263
SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
6364
echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
65+
PLATFORM_TAG=$(echo "${{ matrix.platform }}" | tr '/' '-')
66+
echo "platform_tag=${PLATFORM_TAG}" >> $GITHUB_OUTPUT
6467
65-
- name: 🐳 Build and push multi-platform image
68+
- name: 🐳 Build and push platform-specific image
6669
uses: docker/build-push-action@v5
6770
with:
6871
context: images/esp-idf
69-
platforms: linux/amd64,linux/arm64
72+
platforms: ${{ matrix.platform }}
7073
push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
7174
tags: |
72-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:latest
73-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }}
74-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:idf-${{ matrix.idf_base_tag }}
75-
cache-from: type=gha,scope=${{ env.ESP_IDF_IMAGE_NAME }}
76-
cache-to: type=gha,mode=max,scope=${{ env.ESP_IDF_IMAGE_NAME }}
75+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:idf-${{ matrix.idf_base_tag }}-${{ steps.tags.outputs.platform_tag }}
76+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }}-${{ steps.tags.outputs.platform_tag }}
77+
cache-from: type=gha,scope=${{ env.ESP_IDF_IMAGE_NAME }}-${{ steps.tags.outputs.platform_tag }}
78+
cache-to: type=gha,mode=max,scope=${{ env.ESP_IDF_IMAGE_NAME }}-${{ steps.tags.outputs.platform_tag }}
7779
build-args: |
7880
IDF_BASE_TAG=${{ matrix.idf_base_tag }}
7981
82+
esp-idf-manifest:
83+
runs-on: ubuntu-latest
84+
timeout-minutes: 10
85+
needs: esp-idf-build
86+
if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'
87+
permissions:
88+
contents: read
89+
packages: write
90+
strategy:
91+
fail-fast: false
92+
matrix:
93+
idf_base_tag: ['v5.4.1']
94+
steps:
95+
- name: 🔐 Log in to GitHub Container Registry
96+
uses: docker/login-action@v3
97+
with:
98+
registry: ${{ env.REGISTRY }}
99+
username: ${{ github.actor }}
100+
password: ${{ secrets.GITHUB_TOKEN }}
101+
102+
- name: 🏷️ Generate tags
103+
id: tags
104+
run: |
105+
SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
106+
echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
107+
108+
- name: 🐳 Create and push multi-arch manifest
109+
run: |
110+
docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:latest \
111+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }} \
112+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:idf-${{ matrix.idf_base_tag }} \
113+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:idf-${{ matrix.idf_base_tag }}-linux-amd64 \
114+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:idf-${{ matrix.idf_base_tag }}-linux-arm64
115+
80116
esp-matter-build:
81117
runs-on: [self-hosted, ubuntu-latest]
82-
timeout-minutes: 60
83-
needs: esp-idf-build
118+
timeout-minutes: 120
119+
needs: esp-idf-manifest
84120
if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'
85121
permissions:
86122
contents: read
87123
packages: write
88124
strategy:
89125
fail-fast: false
90126
matrix:
127+
version: ['idf-v5.4.1-matter-v1.4.2']
128+
platform: ['linux/amd64', 'linux/arm64']
91129
include:
92-
- jethome_idf_base_tag: 'v5.4.1'
130+
- version: 'idf-v5.4.1-matter-v1.4.2'
131+
jethome_idf_base_tag: 'v5.4.1'
93132
esp_matter_version: 'v1.4.2'
94133
steps:
95134
- name: 📥 Checkout repository
@@ -111,19 +150,58 @@ jobs:
111150
run: |
112151
SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
113152
echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
153+
PLATFORM_TAG=$(echo "${{ matrix.platform }}" | tr '/' '-')
154+
echo "platform_tag=${PLATFORM_TAG}" >> $GITHUB_OUTPUT
114155
115-
- name: 🐳 Build and push multi-platform ESP-Matter image
156+
- name: 🐳 Build and push platform-specific ESP-Matter image
116157
uses: docker/build-push-action@v5
117158
with:
118159
context: images/esp-matter
119-
platforms: linux/amd64,linux/arm64
160+
platforms: ${{ matrix.platform }}
120161
push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
121162
tags: |
122-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:latest
123-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }}
124-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:idf-${{ matrix.jethome_idf_base_tag }}-matter-${{ matrix.esp_matter_version }}
125-
cache-from: type=gha,scope=${{ env.ESP_MATTER_IMAGE_NAME }}
126-
cache-to: type=gha,mode=max,scope=${{ env.ESP_MATTER_IMAGE_NAME }}
163+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:idf-${{ matrix.jethome_idf_base_tag }}-matter-${{ matrix.esp_matter_version }}-${{ steps.tags.outputs.platform_tag }}
164+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }}-${{ steps.tags.outputs.platform_tag }}
165+
cache-from: type=gha,scope=${{ env.ESP_MATTER_IMAGE_NAME }}-${{ steps.tags.outputs.platform_tag }}
166+
cache-to: type=gha,mode=max,scope=${{ env.ESP_MATTER_IMAGE_NAME }}-${{ steps.tags.outputs.platform_tag }}
127167
build-args: |
128168
BASE_IMAGE_TAG=idf-${{ matrix.jethome_idf_base_tag }}
129169
ESP_MATTER_VERSION=${{ matrix.esp_matter_version }}
170+
171+
esp-matter-manifest:
172+
runs-on: ubuntu-latest
173+
timeout-minutes: 10
174+
needs: esp-matter-build
175+
if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'
176+
permissions:
177+
contents: read
178+
packages: write
179+
strategy:
180+
fail-fast: false
181+
matrix:
182+
version: ['idf-v5.4.1-matter-v1.4.2']
183+
include:
184+
- version: 'idf-v5.4.1-matter-v1.4.2'
185+
jethome_idf_base_tag: 'v5.4.1'
186+
esp_matter_version: 'v1.4.2'
187+
steps:
188+
- name: 🔐 Log in to GitHub Container Registry
189+
uses: docker/login-action@v3
190+
with:
191+
registry: ${{ env.REGISTRY }}
192+
username: ${{ github.actor }}
193+
password: ${{ secrets.GITHUB_TOKEN }}
194+
195+
- name: 🏷️ Generate tags
196+
id: tags
197+
run: |
198+
SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
199+
echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
200+
201+
- name: 🐳 Create and push multi-arch manifest
202+
run: |
203+
docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:latest \
204+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }} \
205+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:idf-${{ matrix.jethome_idf_base_tag }}-matter-${{ matrix.esp_matter_version }} \
206+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:idf-${{ matrix.jethome_idf_base_tag }}-matter-${{ matrix.esp_matter_version }}-linux-amd64 \
207+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:idf-${{ matrix.jethome_idf_base_tag }}-matter-${{ matrix.esp_matter_version }}-linux-arm64

0 commit comments

Comments
 (0)