Skip to content

Commit 73d6362

Browse files
committed
Add docs on multiarch builds with native runners
1 parent 87c1465 commit 73d6362

1 file changed

Lines changed: 114 additions & 3 deletions

File tree

docs/multi-platform-builds.md

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
Building dev containers to support multiple platforms (aka CPU architectures) is possible with the devcontainers/ci GitHub Action/Azure DevOps Task, but requires other actions/tasks to be run beforehand and has several caveats.
44

5-
## General Notes/Caveats
5+
## Multiplatform with Emulation
6+
7+
### General Notes/Caveats
68

79
- Multiplatform builds utilize emulation to build on architectures not native to the system the build is running on. This will significantly increase build times over native, single architecture builds.
810
- If you are using runCmd, the command will only be run on the architecure of the system the build is running on. This means that, if you are using runCmd to test the image, there may be bugs on the alternate platforms that will not be caught by your test suite. Manual post-build testing is advised.
911
- As of October 2022, all hosted servers for GitHub Actions and Azure Pipelines are x86_64 only. If you want to automatically run runCmd-based tests on your devcontainer on another architecure, you'll need a self-hosted runner on that architecture. It is possible that there will be future support for hosted arm64 machines, see [here for a tracking issue for Linux](https://github.com/actions/runner-images/issues/5631).
1012

11-
## GitHub Actions Example
13+
### GitHub Actions Example
1214

1315
```
1416
name: 'build'
@@ -43,7 +45,7 @@ jobs:
4345
platform: linux/amd64,linux/arm64
4446
```
4547

46-
## Azure DevOps Task Example
48+
### Azure DevOps Task Example
4749

4850
```
4951
trigger:
@@ -72,3 +74,112 @@ jobs:
7274
imageName: UserNameHere/ImageNameHere
7375
platform: linux/amd64,linux/arm64
7476
```
77+
78+
79+
## Multiplatform with native runners
80+
81+
### General notes
82+
83+
- Uses matrix strategy to distribute platform builds across native runners
84+
- Avoids cross-platform emulation for better performance and reliability
85+
- Leverages the `imageDigests` output to capture platform-specific image digests
86+
- Combines all platform-specific images into a single manifest list
87+
88+
### Benefits of Native Runners
89+
90+
Building on native runners instead of using QEMU emulation provides several advantages:
91+
92+
1. **Performance**: Native builds are significantly faster than emulated builds
93+
2. **Reliability**: Some platform-specific operations may not work correctly under emulation
94+
3. **Parallelization**: Building multiple platforms simultaneously reduces overall build time
95+
96+
### How the Matrix Outputs Work
97+
98+
- In Github Actions when jobs run in a matrix, only the last matrix instance to complete can set the value for a given output.
99+
- To work around this, we take the json output from the action, and write it to separate output variables per runner
100+
- `imageDigests: {"linux/amd64": "sha256@abc123"}` gets turned into `IMAGE_DIGEST_linux_amd64=sha256@abc123`
101+
- This approach requires hardcoding the list of output variables
102+
103+
### Github Actions Example
104+
105+
```
106+
name: Build Multi-Platform Images
107+
108+
on:
109+
push:
110+
branches: [ main ]
111+
pull_request:
112+
branches: [ main ]
113+
114+
env:
115+
REGISTRY: ghcr.io
116+
IMAGE_NAME: ${{ github.repository }}
117+
118+
jobs:
119+
# Build images on parallel native runners
120+
build:
121+
runs-on: ubuntu-latest
122+
strategy:
123+
matrix:
124+
platform:
125+
- linux/amd64
126+
- linux/arm64
127+
fail-fast: false
128+
outputs:
129+
IMAGE_DIGEST_linux_amd64: ${{ steps.build.outputs.IMAGE_DIGEST_linux_amd64 }}
130+
IMAGE_DIGEST_linux_arm64: ${{ steps.build.outputs.IMAGE_DIGEST_linux_arm64 }}
131+
steps:
132+
- name: Checkout repository
133+
uses: actions/checkout@v4
134+
135+
- name: Set up Docker Buildx
136+
uses: docker/setup-buildx-action@v3
137+
138+
- name: Login to Container Registry
139+
uses: docker/login-action@v3
140+
with:
141+
registry: ${{ env.REGISTRY }}
142+
username: ${{ github.actor }}
143+
password: ${{ secrets.GITHUB_TOKEN }}
144+
145+
- name: Build and push
146+
id: build
147+
uses: devcontainers/ci@v0.3
148+
with:
149+
imageName: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
150+
platform: ${{ matrix.platform }}
151+
push: always
152+
153+
# Combine all digests from the matrix into a single output
154+
- name: Set matrix outputs
155+
if: always()
156+
run: |
157+
# Extract the digest for this platform from the JSON output
158+
DIGESTS_JSON='${{ steps.build.outputs.imageDigests }}'
159+
PLATFORM="${{ matrix.platform }}"
160+
DIGEST=$(echo $DIGESTS_JSON | jq -r --arg platform "$PLATFORM" '.[$platform]')
161+
echo "IMAGE_DIGEST_${PLATFORM//\//_}=${DIGEST}" >> $GITHUB_OUTPUT
162+
163+
164+
# Create a manifest list from all platform images
165+
manifest:
166+
runs-on: ubuntu-latest
167+
needs: build
168+
steps:
169+
- name: Set up Docker Buildx
170+
uses: docker/setup-buildx-action@v3
171+
172+
- name: Create and push manifest list
173+
run: |
174+
IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
175+
176+
# Create manifest list from each platform's digest
177+
docker buildx imagetools create \
178+
-t ${IMAGE}:latest \
179+
${IMAGE}@${{ needs.build.outputs.IMAGE_DIGEST_linux_amd64 }} \
180+
${IMAGE}@${{ needs.build.outputs.IMAGE_DIGEST_linux_arm64 }}
181+
182+
- name: Inspect manifest
183+
run: |
184+
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
185+
```

0 commit comments

Comments
 (0)