Skip to content

Commit 4a8df34

Browse files
committed
Improve GitHub Actions workflows with enhanced fork support and reliability
Workflow improvements: - Allow forks to test builds via workflow_dispatch while preventing automatic builds - Strengthen push conditions to prevent fork pushes to main GHCR - Add timeout protection (60min for builds, 10min for manifests) - Add fail-fast: false to show all platform failures - Add ESP-Matter paths to esp-idf workflow triggers - Rename IMAGE_NAME to PLATFORMIO_IMAGE_NAME for consistency Documentation: - Update Fork Prevention section with new pattern - Update workflow templates with all improvements - Add comprehensive Push Behavior table covering all scenarios - Document timeout and fail-fast best practices Why these changes: - Forks can now test builds manually without wasting Actions minutes - Secure: Only main repo on master can push to GHCR - Better debugging: See all platform failures, not just first - Consistency: All workflows follow same patterns
1 parent b1be116 commit 4a8df34

File tree

3 files changed

+92
-34
lines changed

3 files changed

+92
-34
lines changed

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

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ Standard pattern for image workflows with matrix builds:
7373

7474
```yaml
7575
name: 🐳 <Image Name> Docker Image
76+
#
77+
# Build Strategy:
78+
# - Main repo master: Build + Push to GHCR
79+
# - Main repo dev/PR: Build only (validation)
80+
# - workflow_dispatch: Build on any branch/fork, push only on main repo master
81+
# - Forks: Can test builds, but push is restricted to main repo
7682

7783
on:
7884
push:
@@ -94,11 +100,13 @@ env:
94100
jobs:
95101
<image-name>-build:
96102
runs-on: ubuntu-latest
97-
if: github.repository_owner == 'jethome-iot'
103+
timeout-minutes: 60
104+
if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'
98105
permissions:
99106
contents: read
100107
packages: write
101108
strategy:
109+
fail-fast: false
102110
matrix:
103111
platform: [linux/amd64, linux/arm64]
104112
version: ['1.0.0'] # Can expand to multiple versions
@@ -129,7 +137,7 @@ jobs:
129137
with:
130138
context: images/<image-name>
131139
platforms: ${{ matrix.platform }}
132-
push: ${{ github.ref_name == 'master' }}
140+
push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
133141
tags: |
134142
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.<IMAGE_NAME> }}:<prefix>-${{ matrix.version }}-${{ steps.platform.outputs.tag }}
135143
cache-from: type=gha,scope=${{ env.<IMAGE_NAME> }}-${{ steps.platform.outputs.tag }}
@@ -139,12 +147,14 @@ jobs:
139147

140148
<image-name>-manifest:
141149
runs-on: ubuntu-latest
150+
timeout-minutes: 10
142151
needs: <image-name>-build
143152
if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'
144153
permissions:
145154
contents: read
146155
packages: write
147156
strategy:
157+
fail-fast: false
148158
matrix:
149159
version: ['1.0.0'] # Must match build job matrix
150160
steps:
@@ -179,37 +189,55 @@ jobs:
179189
- **Benefits of image-first naming**: Jobs for same image group together in GitHub UI when sorted alphabetically
180190
- **Image name variables**: Use descriptive uppercase names like `ESP_IDF_IMAGE_NAME`, not generic `IMAGE_NAME`
181191
- **Cache scoping**: Use image name variable for cache scope: `scope=${{ env.<IMAGE_NAME> }}-${{ steps.platform.outputs.tag }}`
192+
- **Timeouts**: Build jobs use `timeout-minutes: 60`, manifest jobs use `timeout-minutes: 10`
193+
- **Fail-fast**: Use `fail-fast: false` to see all platform failures
182194
- Matrix builds both platforms in parallel for faster CI
183195
- Platform-specific images tagged with arch suffix (e.g., `pio-6.1.18-linux-amd64`)
184196
- Manifest job combines platform images into multi-arch manifest
185197
- Only master branch pushes images; dev/PR do build-only validation
186198
- Version matrix can be expanded to build multiple versions
187-
- Simple `workflow_dispatch` trigger allows manual runs from GitHub UI (no parameters)
188-
- **Fork prevention**: `if: github.repository_owner == 'jethome-iot'` prevents builds in forks
199+
- Simple `workflow_dispatch` trigger allows manual runs from any branch/fork
200+
- **Fork prevention**: `if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'` allows fork testing
201+
- **Secure push**: `push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}` prevents fork pushes
189202

190203
### Fork Prevention
191204

192-
**Always add organization check to prevent unnecessary builds in forks:**
205+
**Allow forks to test builds via workflow_dispatch, but prevent automatic builds:**
193206

194207
```yaml
195208
jobs:
196209
<image-name>-build:
197210
runs-on: ubuntu-latest
198-
if: github.repository_owner == 'jethome-iot' # Prevents builds in forks
211+
timeout-minutes: 60
212+
if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'
199213
permissions:
200214
contents: read
201215
packages: write
216+
strategy:
217+
fail-fast: false # Show all platform failures
218+
matrix:
219+
platform: [linux/amd64, linux/arm64]
220+
```
221+
222+
**Strengthen push conditions to prevent fork pushes:**
223+
224+
```yaml
225+
- name: 🐳 Build and push Docker image
226+
uses: docker/build-push-action@v5
227+
with:
228+
push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
202229
```
203230

204-
**Why this is important:**
205-
- ✅ **Saves Actions minutes** - Forks won't waste time building Docker images they don't need
206-
- ✅ **Clearer intent** - Only the main repository publishes images
207-
- ✅ **Still testable** - Forks can remove this line if they want to test
231+
**Why this pattern:**
232+
- ✅ **Saves Actions minutes** - Forks don't trigger automatic builds on push/PR
233+
- ✅ **Allows testing** - Forks can manually run workflow_dispatch to test
234+
- ✅ **Secure pushes** - Only main repo on master can push to GHCR
208235
- ✅ **Common practice** - Standard pattern for OSS projects with Docker registries
209236

210237
**Apply to all jobs:**
211-
- Build jobs (`<image-name>-build`): `if: github.repository_owner == 'jethome-iot'`
212-
- Manifest jobs (`<image-name>-manifest`): `if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'`
238+
- Build jobs: `if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'`
239+
- Manifest jobs: `if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'`
240+
- Push conditions: `push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}`
213241

214242
### Version Handling
215243

@@ -222,15 +250,19 @@ jobs:
222250

223251
| Branch/Event | Build | Push to GHCR | Tags Generated |
224252
|--------------|-------|--------------|----------------|
225-
| Master | Yes | Yes | `latest`, `stable`, `VERSION`, `sha-SHA` |
226-
| Dev | Yes | No | None |
227-
| Pull Request | Yes | No | None |
253+
| Main repo master push | Yes | Yes | `latest`, `stable`, `VERSION`, `sha-SHA`, `date` |
254+
| Main repo dev push | Yes | No | None |
255+
| Main repo PR | Yes | No | None |
256+
| Main repo workflow_dispatch | Yes | Only on master | Same as master if on master |
257+
| Fork push/PR | No | No | None (automatic builds disabled) |
258+
| Fork workflow_dispatch | Yes | No | None (push blocked by condition) |
228259

229260
**Rationale:**
230-
- Keeps GHCR clean with only released images
261+
- Keeps GHCR clean with only released images from main repo
231262
- Faster CI on dev branches (build-only validation)
232263
- Clear release strategy: master = production/stable
233-
- Dev branch for testing before release
264+
- Forks can test builds manually, but automatic builds are disabled
265+
- Secure: Only main repo on master can push to GHCR
234266

235267
## Workflow Testing
236268

.github/workflows/esp-idf.yml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# ESP-IDF Docker Build Workflow
22
# Builds ESP-IDF development image with pytest, QEMU, and testing tools
3+
#
4+
# Build Strategy:
5+
# - Main repo master: Build + Push to GHCR
6+
# - Main repo dev/PR: Build only (validation)
7+
# - workflow_dispatch: Build on any branch/fork, push only on main repo master
8+
# - Forks: Can test builds, but push is restricted to main repo
39
name: 🐳 ESP-IDF Docker Image
410

511
on:
@@ -8,11 +14,13 @@ on:
814
paths:
915
- '.github/workflows/esp-idf.yml'
1016
- 'images/esp-idf/**'
17+
- 'images/esp-matter/**'
1118
pull_request:
1219
branches: [master, dev]
1320
paths:
1421
- '.github/workflows/esp-idf.yml'
1522
- 'images/esp-idf/**'
23+
- 'images/esp-matter/**'
1624
workflow_dispatch:
1725

1826
env:
@@ -23,11 +31,13 @@ env:
2331
jobs:
2432
esp-idf-build:
2533
runs-on: ubuntu-latest
26-
if: github.repository_owner == 'jethome-iot'
34+
timeout-minutes: 60
35+
if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'
2736
permissions:
2837
contents: read
2938
packages: write
3039
strategy:
40+
fail-fast: false
3141
matrix:
3242
platform: [linux/amd64, linux/arm64]
3343
idf_base_tag: ['v5.4.1']
@@ -58,7 +68,7 @@ jobs:
5868
with:
5969
context: images/esp-idf
6070
platforms: ${{ matrix.platform }}
61-
push: ${{ github.ref_name == 'master' }}
71+
push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
6272
tags: |
6373
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_IDF_IMAGE_NAME }}:idf-${{ matrix.idf_base_tag }}-${{ steps.platform.outputs.tag }}
6474
cache-from: type=gha,scope=${{ env.ESP_IDF_IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
@@ -68,12 +78,14 @@ jobs:
6878
6979
esp-idf-manifest:
7080
runs-on: ubuntu-latest
81+
timeout-minutes: 10
7182
needs: esp-idf-build
7283
if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'
7384
permissions:
7485
contents: read
7586
packages: write
7687
strategy:
88+
fail-fast: false
7789
matrix:
7890
idf_base_tag: ['v5.4.1']
7991
steps:
@@ -104,12 +116,14 @@ jobs:
104116
105117
esp-matter-build:
106118
runs-on: ubuntu-latest
119+
timeout-minutes: 60
107120
needs: esp-idf-build
108-
if: false && github.repository_owner == 'jethome-iot' # Temporarily disabled
121+
if: false && (github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch') # Temporarily disabled
109122
permissions:
110123
contents: read
111124
packages: write
112125
strategy:
126+
fail-fast: false
113127
matrix:
114128
platform: [linux/amd64, linux/arm64]
115129
jethome_idf_base_tag: ['v5.4.1']
@@ -141,7 +155,7 @@ jobs:
141155
with:
142156
context: images/esp-matter
143157
platforms: ${{ matrix.platform }}
144-
push: ${{ github.ref_name == 'master' }}
158+
push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
145159
tags: |
146160
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.ESP_MATTER_IMAGE_NAME }}:idf-${{ matrix.jethome_idf_base_tag }}-matter-${{ matrix.esp_matter_version }}-${{ steps.platform.outputs.tag }}
147161
cache-from: type=gha,scope=${{ env.ESP_MATTER_IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
@@ -152,12 +166,14 @@ jobs:
152166
153167
esp-matter-manifest:
154168
runs-on: ubuntu-latest
169+
timeout-minutes: 10
155170
needs: esp-matter-build
156171
if: false && github.repository_owner == 'jethome-iot' && github.ref_name == 'master' # Temporarily disabled
157172
permissions:
158173
contents: read
159174
packages: write
160175
strategy:
176+
fail-fast: false
161177
matrix:
162178
jethome_idf_base_tag: ['v5.4.1']
163179
esp_matter_version: ['v1.4.2']

.github/workflows/platformio.yml

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
# PlatformIO Docker Build Workflow
2+
#
3+
# Build Strategy:
4+
# - Main repo master: Build + Push to GHCR
5+
# - Main repo dev/PR: Build only (validation)
6+
# - workflow_dispatch: Build on any branch/fork, push only on main repo master
7+
# - Forks: Can test builds, but push is restricted to main repo
28
name: 🐳 PlatformIO Docker Image
39

410
on:
@@ -16,16 +22,18 @@ on:
1622

1723
env:
1824
REGISTRY: ghcr.io
19-
IMAGE_NAME: jethome-dev-platformio
25+
PLATFORMIO_IMAGE_NAME: jethome-dev-platformio
2026

2127
jobs:
2228
platformio-build:
2329
runs-on: ubuntu-latest
24-
if: github.repository_owner == 'jethome-iot'
30+
timeout-minutes: 60
31+
if: github.repository_owner == 'jethome-iot' || github.event_name == 'workflow_dispatch'
2532
permissions:
2633
contents: read
2734
packages: write
2835
strategy:
36+
fail-fast: false
2937
matrix:
3038
platform: [linux/amd64, linux/arm64]
3139
pio_version: ['6.1.18']
@@ -56,22 +64,24 @@ jobs:
5664
with:
5765
context: images/platformio
5866
platforms: ${{ matrix.platform }}
59-
push: ${{ github.ref_name == 'master' }}
67+
push: ${{ github.repository_owner == 'jethome-iot' && github.ref_name == 'master' }}
6068
tags: |
61-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:pio-${{ matrix.pio_version }}-${{ steps.platform.outputs.tag }}
62-
cache-from: type=gha,scope=${{ env.IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
63-
cache-to: type=gha,mode=max,scope=${{ env.IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
69+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}:pio-${{ matrix.pio_version }}-${{ steps.platform.outputs.tag }}
70+
cache-from: type=gha,scope=${{ env.PLATFORMIO_IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
71+
cache-to: type=gha,mode=max,scope=${{ env.PLATFORMIO_IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
6472
build-args: |
6573
PIO_VERSION=${{ matrix.pio_version }}
6674
6775
platformio-manifest:
6876
runs-on: ubuntu-latest
77+
timeout-minutes: 10
6978
needs: platformio-build
7079
if: github.repository_owner == 'jethome-iot' && github.ref_name == 'master'
7180
permissions:
7281
contents: read
7382
packages: write
7483
strategy:
84+
fail-fast: false
7585
matrix:
7686
pio_version: ['6.1.18']
7787
steps:
@@ -92,10 +102,10 @@ jobs:
92102
93103
- name: 🎯 Create multi-arch manifest
94104
run: |
95-
docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest \
96-
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:stable \
97-
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:pio-${{ matrix.pio_version }} \
98-
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.date_tag }} \
99-
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }} \
100-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:pio-${{ matrix.pio_version }}-linux-amd64 \
101-
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:pio-${{ matrix.pio_version }}-linux-arm64
105+
docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}:latest \
106+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}:stable \
107+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}:pio-${{ matrix.pio_version }} \
108+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}:${{ steps.tags.outputs.date_tag }} \
109+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }} \
110+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}:pio-${{ matrix.pio_version }}-linux-amd64 \
111+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}:pio-${{ matrix.pio_version }}-linux-arm64

0 commit comments

Comments
 (0)