@@ -34,92 +34,128 @@ 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 reusable workflow: `.github/workflows/docker-build.yml`
37+ - Use inline matrix builds for multi-platform support (amd64/arm64)
3838- Images are **only pushed to GHCR from master branch**
3939- Dev and PR branches build only (no push)
4040
4141### Workflow Structure
4242
43- Standard pattern for image workflows:
43+ Standard pattern for image workflows with matrix builds :
4444
4545```yaml
4646name: 🐳 <Image Name> Docker Image
4747
48- env:
49- DEFAULT_VERSION: v1.0.0
50-
5148on:
5249 push:
5350 branches: [master, dev]
5451 paths:
5552 - '.github/workflows/<image-name>.yml'
56- - '.github/workflows/docker-build.yml'
5753 - 'images/<image-name>/**'
5854 pull_request:
5955 branches: [master, dev]
6056 paths:
6157 - '.github/workflows/<image-name>.yml'
62- - '.github/workflows/docker-build.yml'
6358 - 'images/<image-name>/**'
64- workflow_dispatch:
65- inputs:
66- version:
67- description: 'Version (e.g., v1.0.0)'
68- required: true
69- type: string
70- force_rebuild:
71- description: 'Force rebuild without cache'
72- required: false
73- default: false
74- type: boolean
75- custom_tags:
76- description: 'Additional custom tags (comma-separated, optional)'
77- required: false
78- default: ''
79- type: string
59+
60+ env:
61+ REGISTRY: ghcr.io
62+ IMAGE_NAME: jethome-dev-<image-name>
8063
8164jobs:
82- prepare :
65+ build :
8366 runs-on: ubuntu-latest
84- outputs:
85- version: ${{ steps.version.outputs.version }}
67+ permissions:
68+ contents: read
69+ packages: write
70+ strategy:
71+ matrix:
72+ platform: [linux/amd64, linux/arm64]
73+ version: ['1.0.0'] # Can expand to multiple versions
8674 steps:
87- - name: 🏷️ Resolve version
88- id: version
75+ - name: 📥 Checkout repository
76+ uses: actions/checkout@v4
77+
78+ - name: 🔧 Set up Docker Buildx
79+ uses: docker/setup-buildx-action@v3
80+
81+ - name: 🔐 Log in to GitHub Container Registry
82+ if: github.ref_name == 'master'
83+ uses: docker/login-action@v3
84+ with:
85+ registry: ${{ env.REGISTRY }}
86+ username: ${{ github.actor }}
87+ password: ${{ secrets.GITHUB_TOKEN }}
88+
89+ - name: 🏷️ Prepare platform tag
90+ id: platform
8991 run: |
90- VERSION="${{ github.event.inputs.version || env.DEFAULT_VERSION }}"
91- echo "version=${VERSION}" >> $GITHUB_OUTPUT
92- echo "Using version: ${VERSION}"
93-
94- build:
95- needs: prepare
96- uses: ./.github/workflows/docker-build.yml
92+ PLATFORM_PAIR=${{ matrix.platform }}
93+ PLATFORM_TAG=$(echo $PLATFORM_PAIR | sed 's/\//-/g')
94+ echo "tag=${PLATFORM_TAG}" >> $GITHUB_OUTPUT
95+
96+ - name: 🐳 Build and push Docker image
97+ uses: docker/build-push-action@v5
98+ with:
99+ context: images/<image-name>
100+ platforms: ${{ matrix.platform }}
101+ push: ${{ github.ref_name == 'master' }}
102+ tags: |
103+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:<prefix>-${{ matrix.version }}-${{ steps.platform.outputs.tag }}
104+ cache-from: type=gha,scope=${{ env.IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
105+ cache-to: type=gha,mode=max,scope=${{ env.IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
106+ build-args: |
107+ VERSION=${{ matrix.version }}
108+
109+ create-manifest:
110+ runs-on: ubuntu-latest
111+ needs: build
112+ if: github.ref_name == 'master'
97113 permissions:
98114 contents: read
99115 packages: write
100- secrets: inherit
101- with:
102- image_name: jethome-dev-<image-name>
103- context_path: images/<image-name>
104- version_tag: <prefix>-${{ needs.prepare.outputs.version }}
105- force_rebuild: ${{ github.event.inputs.force_rebuild == 'true' }}
106- custom_tags: ${{ github.event.inputs.custom_tags || '' }}
107- build_args: |
108- VERSION=${{ needs.prepare.outputs.version }}
116+ strategy:
117+ matrix:
118+ version: ['1.0.0'] # Must match build job matrix
119+ steps:
120+ - name: 🔐 Log in to GitHub Container Registry
121+ uses: docker/login-action@v3
122+ with:
123+ registry: ${{ env.REGISTRY }}
124+ username: ${{ github.actor }}
125+ password: ${{ secrets.GITHUB_TOKEN }}
126+
127+ - name: 🏷️ Generate version and date tags
128+ id: tags
129+ run: |
130+ SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
131+ DATE_TAG=$(date +%Y.%m.%d)
132+ echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
133+ echo "date_tag=${DATE_TAG}" >> $GITHUB_OUTPUT
134+
135+ - name: 🎯 Create multi-arch manifest
136+ run: |
137+ docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest \
138+ -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:stable \
139+ -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:<prefix>-${{ matrix.version }} \
140+ -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.date_tag }} \
141+ -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }} \
142+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:<prefix>-${{ matrix.version }}-linux-amd64 \
143+ ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:<prefix>-${{ matrix.version }}-linux-arm64
109144```
110145
111146**Critical Details:**
112- - `secrets: inherit` - Required for reusable workflows to access GITHUB_TOKEN
113- - `force_rebuild: ${{ ... == 'true' }}` - Convert string to boolean (not `|| false`)
114- - Boolean inputs from workflow_dispatch come as strings and need explicit comparison
147+ - Matrix builds both platforms in parallel for faster CI
148+ - Platform-specific images tagged with arch suffix (e.g., `pio-6.1.18-linux-amd64`)
149+ - Manifest job combines platform images into multi-arch manifest
150+ - Only master branch pushes images; dev/PR do build-only validation
151+ - Version matrix can be expanded to build multiple versions
115152
116153### Version Handling
117154
118- - Define default versions in `env` at workflow level (single source of truth)
119- - Use `prepare` job to resolve version (inputs or env default)
120- - Pass resolved version via job outputs to build job
121- - `env` context available in job steps, outputs available in `with:`
122- - Easy to override manually via GitHub UI (workflow_dispatch inputs)
155+ - Define versions directly in matrix strategy
156+ - Can expand to multiple versions: `version: ['1.0.0', '1.1.0']`
157+ - Each version builds for all platforms in the matrix
158+ - Matrix generates all combinations: platforms × versions
123159
124160### Push Behavior by Branch
125161
@@ -149,23 +185,19 @@ Test workflows locally using `act`:
149185
150186Use `gh` CLI to test workflows on GitHub Actions:
151187
152- **Manual Workflow Dispatch:**
188+ **Triggering Workflows:**
189+
190+ Workflows are triggered automatically by pushing to master or dev branches. To test:
191+
153192```bash
154- # Trigger workflow with parameters
155- gh workflow run "🐳 <Workflow Name>" --ref master \
156- -f version=v1.0.0 \
157- -f force_rebuild=true \
158- -f custom_tags=test
159-
160- # Example: ESP-IDF
161- gh workflow run "🐳 ESP-IDF Docker Image" --ref master \
162- -f esp_idf_version=v5.4.1 \
163- -f force_rebuild=true
164-
165- # Example: PlatformIO
166- gh workflow run "🐳 PlatformIO Docker Image" --ref master \
167- -f pio_version=6.1.18 \
168- -f force_rebuild=false
193+ # Push to dev branch for testing (build-only, no push to GHCR)
194+ git checkout dev
195+ git push origin dev
196+
197+ # After validation, merge to master (builds and pushes to GHCR)
198+ git checkout master
199+ git merge dev
200+ git push origin master
169201```
170202
171203**Monitoring Workflows:**
@@ -199,62 +231,41 @@ gh api /repos/:owner/:repo/actions/runs/<run-id>/jobs | \
199231 jq -r '.jobs[] | "\(.name): \(.status) - \(.conclusion // "N/A")"'
200232```
201233
202- **Automated Testing Script Example :**
234+ **Monitoring Latest Run :**
203235```bash
204- #!/bin/bash
205- # Trigger workflow and wait for completion
206-
236+ # Get latest workflow run for a specific workflow
207237WORKFLOW="🐳 ESP-IDF Docker Image"
208- VERSION="v5.4.1"
209-
210- # Trigger workflow
211- gh workflow run "$WORKFLOW" --ref master \
212- -f esp_idf_version=$VERSION \
213- -f force_rebuild=true
214-
215- # Wait for workflow to appear
216- sleep 10
217-
218- # Get latest run ID
219238RUN_ID=$(gh run list --workflow="$WORKFLOW" --limit 1 \
220239 --json databaseId -q '.[0].databaseId')
221240
222- echo "Monitoring workflow run: $RUN_ID"
223-
224241# Monitor until completion
225- while true; do
226- STATUS=$(gh run view $RUN_ID --json status,conclusion | \
227- jq -r '.status')
228-
229- if [ "$STATUS" = "completed" ]; then
230- CONCLUSION=$(gh run view $RUN_ID --json conclusion | \
231- jq -r '.conclusion')
232- echo "Workflow completed: $CONCLUSION"
233-
234- if [ "$CONCLUSION" = "success" ]; then
235- exit 0
236- else
237- gh run view $RUN_ID --log-failed
238- exit 1
239- fi
240- fi
241-
242- echo "Status: $STATUS"
243- sleep 20
244- done
242+ gh run watch $RUN_ID
243+
244+ # Check final status
245+ CONCLUSION=$(gh run view $RUN_ID --json conclusion -q '.conclusion')
246+ if [ "$CONCLUSION" = "success" ]; then
247+ echo "✅ Workflow succeeded"
248+ else
249+ echo "❌ Workflow failed"
250+ gh run view $RUN_ID --log-failed
251+ fi
245252```
246253
247254### Testing Best Practices
248255
2492561. **Local first**: Test with `act` before pushing
250- 2. **Force rebuild**: Use `force_rebuild=true` to bypass Docker cache when testing fixes
251- - Required when Dockerfile changes but Docker might use cached layers
252- - The `no-cache` option in reusable workflow ensures true rebuild
257+ 2. **Branch strategy**: Always test on `dev` branch first
258+ - Push to dev for build validation (no GHCR push)
259+ - Review workflow logs for both platforms
260+ - Merge to master only after successful dev validation
2532613. **Monitor logs**: Always check logs for build errors, even on "success"
254- - Check both amd64 and arm64 build logs if multi-platform
262+ - Check both amd64 and arm64 build logs (matrix creates separate jobs)
255263 - Look for 0.1s completion times (indicates QEMU failure on arm64)
256- 4. **Verify image**: Pull and test the published image after successful build
257- - Test with actual commands: `docker run --rm <image> pytest --version`
258- - Verify imports work: `python3 -c 'import pytest_embedded'`
259- 5. **Branch strategy**: Test on `dev` branch first, then merge to `master` for release
260- - Remember: dev builds don't push to GHCR (build validation only)
264+ - Verify both platform builds complete successfully
265+ 4. **Verify images**: Pull and test the published image after successful master build
266+ - Test multi-arch manifest: `docker pull <image>:latest`
267+ - Verify on your architecture: `docker run --rm <image>:latest <command>`
268+ - Check that correct platform is pulled automatically
269+ 5. **Cache management**: GitHub Actions cache persists between runs
270+ - Cache is per-platform (separate for amd64/arm64)
271+ - To force fresh build, clear GitHub Actions cache via Settings → Actions → Caches
0 commit comments