|
2 | 2 |
|
3 | 3 | 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. |
4 | 4 |
|
5 | | -## General Notes/Caveats |
| 5 | +## Multiplatform with Emulation |
| 6 | + |
| 7 | +### General Notes/Caveats |
6 | 8 |
|
7 | 9 | - 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. |
8 | 10 | - 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. |
9 | 11 | - 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). |
10 | 12 |
|
11 | | -## GitHub Actions Example |
| 13 | +### GitHub Actions Example |
12 | 14 |
|
13 | 15 | ``` |
14 | 16 | name: 'build' |
|
43 | 45 | platform: linux/amd64,linux/arm64 |
44 | 46 | ``` |
45 | 47 |
|
46 | | -## Azure DevOps Task Example |
| 48 | +### Azure DevOps Task Example |
47 | 49 |
|
48 | 50 | ``` |
49 | 51 | trigger: |
@@ -72,3 +74,112 @@ jobs: |
72 | 74 | imageName: UserNameHere/ImageNameHere |
73 | 75 | platform: linux/amd64,linux/arm64 |
74 | 76 | ``` |
| 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