|
| 1 | +--- |
| 2 | +title: "Prebuild dev containers with GitHub Actions" |
| 3 | +meta_title: "Prebuild dev containers with GitHub Actions" |
| 4 | +description: "In this article, we are using the 'devcontainers/ci' action to prebuild dev containers for your project." |
| 5 | +date: 2025-03-03T05:00:00Z |
| 6 | +image: "/images/posts/prebuild.jpg" |
| 7 | +categories: ["github"] |
| 8 | +authors: ["Bas Steins"] |
| 9 | +tags: ["vscode", "codespaces", "github-actions"] |
| 10 | +draft: true |
| 11 | +--- |
| 12 | + |
| 13 | +Development Containers (or DevContainers) are a great way to ensure that everyone working on a project has the same development environment. This is especially useful when working with a team or when you want to make sure that your project can be built and run on any machine. In this article, we are using the `devcontainers/ci` action to prebuild dev containers for your project. |
| 14 | + |
| 15 | +## What are DevContainers? |
| 16 | + |
| 17 | +DevContainers are a way to define a development environment using a `devcontainer.json` file. This file specifies the base image, the extensions that should be installed, and any other settings that are needed to set up the development environment. When you open a project in Visual Studio Code, it will automatically detect the `devcontainer.json` file and set up the development environment for you. |
| 18 | + |
| 19 | +## Prebuilding DevContainers |
| 20 | + |
| 21 | +A `devcontainer.json` file can get arbitrarily complex. Especially, when you are using a custom `Dockerfile` for your project, build times can get quite long. This is where prebuilding dev containers comes in handy. By prebuilding the dev container, you can reduce the time it takes to set up the development environment for your project. |
| 22 | + |
| 23 | +We are using the `devcontainers/ci` action, so that on each `push` to your repository, the dev container is built and pushed to the GitHub Container Registry. This way, the dev container is ready to be used by anyone who wants to work on your project. |
| 24 | + |
| 25 | +In our example, we use a custom docker file with Python 3.12 installed via `pyenv` (which involved compiling). We want to target the `amd64` and `arm64` architectures, so that the image can run natively on Apple Silicon and Intel/AMD based PCs. |
| 26 | + |
| 27 | +## The workflow yaml file |
| 28 | + |
| 29 | +```yaml |
| 30 | +name: Pre-build Devcontainer |
| 31 | + |
| 32 | +on: |
| 33 | + workflow_dispatch: |
| 34 | + push: |
| 35 | + branches: |
| 36 | + - main |
| 37 | + |
| 38 | +jobs: |
| 39 | + build_devcontainer: |
| 40 | + runs-on: ubuntu-24.04 |
| 41 | + steps: |
| 42 | + - name: Checkout code |
| 43 | + uses: actions/checkout@v4 |
| 44 | + |
| 45 | + - name: Set up QEMU |
| 46 | + uses: docker/setup-qemu-action@v3 |
| 47 | + |
| 48 | + - name: Set up Docker Buildx |
| 49 | + uses: docker/setup-buildx-action@v3 |
| 50 | + with: |
| 51 | + version: v0.16.1 |
| 52 | + platforms: linux/amd64,linux/arm64 |
| 53 | + install: true |
| 54 | + use: true |
| 55 | + |
| 56 | + - name: Login to GitHub Container Registry |
| 57 | + uses: docker/login-action@v3 |
| 58 | + with: |
| 59 | + registry: ghcr.io |
| 60 | + username: ${{ github.repository_owner }} |
| 61 | + password: ${{ secrets.GITHUB_TOKEN }} |
| 62 | + |
| 63 | + - name: Pre-build dev container image |
| 64 | + uses: devcontainers/[email protected] |
| 65 | + with: |
| 66 | + imageName: ghcr.io/bascodes/prebuild-devcontainer-gha |
| 67 | + # cacheTo: type=inline # This is the default value. |
| 68 | + runCmd: sleep 1 |
| 69 | + push: always |
| 70 | + platform: linux/amd64,linux/arm64 |
| 71 | +``` |
| 72 | +
|
| 73 | +This workflow sets up Docker's `buildx`, and `qemu`, which is needed to build for two architectures. |
| 74 | + |
| 75 | +**Note**: Our simple setup takes about an hour to build on GitHub Actions runners. This is because we're using `qemu` to emulate the `arm64` architecture. If you're rebuilding the image frequently, you might want to use dedicated `arm` and `amd` runners to speed up the build process. |
| 76 | + |
| 77 | +### The `devcontainers/ci` action |
| 78 | + |
| 79 | +The `devcontainers/ci` action is a GitHub Action that builds and pushes a Docker image to the GitHub Container Registry. It is a simple way to prebuild dev containers for your project since it automatically detects the `devcontainer.json` file and builds the image based on the settings in that file. |
| 80 | + |
| 81 | +**Note**: The most recent version of the action automatically sets the `--cache-to type=inline` flag, which caches the image layers on the runner. In more complex setups, you might want to provide a dedicated cache image like this: |
| 82 | + |
| 83 | +```yaml |
| 84 | + - name: Pre-build dev container image |
| 85 | + uses: devcontainers/[email protected] |
| 86 | + with: |
| 87 | + imageName: ghcr.io/bascodes/prebuild-devcontainer-gha |
| 88 | + cacheTo: type=registry,ref=ghcr.io/bascodes/prebuild-devcontainer-gha:cache |
| 89 | + runCmd: sleep 1 |
| 90 | + push: always |
| 91 | + platform: linux/amd64,linux/arm64 |
| 92 | +``` |
| 93 | + |
| 94 | +## The `devcontainer.json` file |
| 95 | + |
| 96 | +```json |
| 97 | +{ |
| 98 | + "name": "devcontainers-ci", |
| 99 | + "build": { |
| 100 | + "dockerfile": "./Dockerfile", |
| 101 | + "context": ".", |
| 102 | + "cacheFrom": "type=registry,ref=ghcr.io/bascodes/prebuild-devcontainer-gha:latest" |
| 103 | + }, |
| 104 | + "remoteUser": "root", |
| 105 | + "features": { |
| 106 | + "ghcr.io/devcontainers/features/github-cli:1": "latest" |
| 107 | + } |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +Here, we specify that the dev container should be build according to a custom `Dockerfile`. We also specify a `cacheTo` argument to the `build` options. |
| 112 | + |
| 113 | +This is the exact cache layer that we build using the GitHub Action Workflow above. |
| 114 | + |
| 115 | +**Note**: When setting up your repository with a `devcontainer.json` file and a Github Actions workflow, please remove the `cacheFrom` argument from the `devcontainer.json` file at first. On the first run, there is no image to cache from. Once your workflow ran once successfully, you can add the `cacheFrom` argument to the `devcontainer.json` file. |
| 116 | + |
| 117 | + |
| 118 | +## Seeing it in action |
| 119 | + |
| 120 | +Check out the Github repository at [bascodes/prebuild-devcontainer-gha](https://github.com/bascodes/prebuild-devcontainer-gha) to see the workflow in action. |
| 121 | + |
| 122 | +Once, you open it in VSCode, you will be prompted to open the project in a dev container. The dev container will be pulled from the GitHub Container Registry and set up in your local environment. |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | +Once you click that, the dev container will be pulled from the GitHub Container Registry and set up in your local environment. |
| 127 | + |
| 128 | +You can verify that the build process actually used the cache by checking the logs of the GitHub Actions workflow. |
| 129 | + |
| 130 | + |
| 131 | + |
| 132 | +All the layers should be marked with `CACHED`. |
| 133 | + |
| 134 | + |
| 135 | +## Conclusion |
| 136 | + |
| 137 | +Prebuilding dev containers is a great way to speed up the setup of your development environment. By using the `devcontainers/ci` action, you can easily build and push the dev container to the GitHub Container Registry. This way, the dev container is ready to be used by anyone who wants to work on your project. |
0 commit comments