Build devcontainer images using remote Docker cache. This repo contains definitions for a complete workflow around the devcontainer image, including configuration for local building and cache population by CI pipeline.
This repo defines tooling for the entire lifecycle of a devcontainer cache. Based on the context of the script call, devcontainer-cache-build-initialize
supplies build configuration to target cache to refs for each event in that lifecycle, as described below:
graph TD;
gha_main_workflow(GitHub Actions main workflow) <--1--> main_cache(registry/image-cache:main-layer)
gha_pr_workflow(GitHub Actions PR workflow) <--2--> branch_cache(registry/image-cache:branch-layer)
devcontainer_build(devcontainer build)
devcontainer_build <--3--> local_branch_cache(registry/image-cache:local-branch-layer)
main_cache --4--> gha_pr_workflow & devcontainer_build
branch_cache --5--> gha_main_workflow & devcontainer_build
local_branch_cache --6--> gha_main_workflow & gha_main_workflow
subgraph Registry
main_cache
branch_cache
local_branch_cache
end
subgraph GitHub Actions
gha_main_workflow
gha_pr_workflow
end
- GitHub Actions default branch workflows populate the cache for each layer, under the
main
ref. - GitHub Actions PR workflows populate the cache for each layer, under the ref for the PR branch name.
- Devcontainer builds populate the cache for each layer, under the ref for the local branch name, with a
local-
prefix. - The
main
ref cache for each layer is used for builds in all contexts. - The branch ref cache for each layer is used for builds in all contexts.
- The
local-
branch ref cache for each layer is used for builds in all contexts.
The initialize script replaces build
in .devcontainer/devcontainer.json
, and computes appropriate configuration for the devcontainer image Docker build command. Internally, it wraps a Docker container run, in which the configuration logic and build execution is managed.
To use the script, add "initializeCommand": "curl https://raw.githubusercontent.com/rcwbr/devcontainer-cache-build/0.5.0/devcontainer-cache-build-initialize | bash"
to your devcontainer.json
, or to scripts referenced by it. For example, you might replace
{
...
"build": {
"dockerfile": "Dockerfile"
},
...
}
with
{
...
"initializeCommand": ".devcontainer/initialize",
"image": "my-project-devcontainer"
...
}
and .devcontainer/initialize
:
#!/bin/bash
export DEVCONTAINER_IMAGE=my-project-devcontainer
curl https://raw.githubusercontent.com/rcwbr/devcontainer-cache-build/0.5.0/devcontainer-cache-build-initialize | bash
A specific version of the script may be used by adjusting the URL in the curl
command. The format is https://raw.githubusercontent.com/rcwbr/devcontainer-cache-build/<version ref>/devcontainer-cache-build-initialize
, where <version ref>
may be any valid version reference from this repo.
To use the devcontainer-cache-build-initialize
script with bake, the recommended usage is to set the DEVCONTAINER_BUILD_ADDITIONAL_ARGS
and DEVCONTAINER_DEFINITION_FILES
vars to leverage bake file partials as defined in the dockerfile-partials repository.
If using a custom bake file, the config must contain the following configuration:
variable "DEVCONTAINER_OUTPUTS" {
default = ""
}
variable "DEVCONTAINER_CACHE_FROMS" {
default = ""
}
variable "DEVCONTAINER_CACHE_TOS" {
default = ""
}
target "default" {
dockerfile = ".devcontainer/Dockerfile"
cache-from = [
for cache_from in split(" ", trimspace("${DEVCONTAINER_CACHE_FROMS}")):
"${cache_from}-base"
]
cache-to = [
for cache_to in split(" ", trimspace("${DEVCONTAINER_CACHE_TOS}")):
"${cache_to}-base"
]
output = split(" ", trimspace("${DEVCONTAINER_OUTPUTS}"))
}
The devcontainer-cache-build-initialize
script reads several environment variables as configuration.
Variable | Required | Default | Effect |
---|---|---|---|
DEVCONTAINER_IMAGE |
✓ | N/A | The tag applied to the image build |
DEVCONTAINER_BUILD_ADDITIONAL_ARGS |
✗ | N/A | Arbitrary additional args forwarded to the build or bake command, as comma-separated key=value pairs in Python-on-whales syntax |
DEVCONTAINER_CACHE_BUILD_OVERRIDE_* |
✗ | None | Special env variables to pass through to the build execution context, that may not be exportable. Supported variables are USER , UID , and USER_GID (e.g. DEVCONTAINER_CACHE_BUILD_OVERRIDE_USER=my_username ...) |
DEVCONTAINER_CACHE_FROMS |
✗ | type=registry,ref=[DEVCONTAINER_REGISTRY]-cache:[current git branch name sanitized] type=registry,ref=[DEVCONTAINER_REGISTRY]-cache:local-[current git branch name sanitized] type=registry,ref=[DEVCONTAINER_REGISTRY]-cache:[DEVCONTAINER_DEFAULT_BRANCH_NAME, sanitized] |
Each cache-from arg to be applied to the image build, space separated |
DEVCONTAINER_CACHE_TOS |
✗ | type=registry,rewrite-timestamp=true,mode=max,ref=[DEVCONTAINER_REGISTRY]-cache:[local-][current git branch name sanitized] |
Each cache-to arg to be applied to the image build, space separated. The default value includes local- applied as a version prefix unless CI=true |
DEVCONTAINER_CONTEXT |
✗ | . |
The build context for the image |
DEVCONTAINER_DEFAULT_BRANCH_NAME |
✗ | main |
The branch name from which to always pull cache |
DEVCONTAINER_DEFINITION_TYPE |
✗ | build |
The image definition type, basic Docker build (build ) or Bake (bake ) |
DEVCONTAINER_DEFINITION_FILES |
✗ | .devcontainer/Dockerfile , or .devcontainer/bake.hcl if DEVCONTAINER_DEFINITION_TYPE is bake |
The Dockerfile or bake config file path(s) for the image build, space separated |
DEVCONTAINER_INITIALIZE_PID |
✗ | N/A | If defined, must be set to the process ID of the command provided to the devcontainer.json initializeCommand (often $PPID ). Used to determine whether the context of the initializeCommand call is a new container bringup, based on the presence of the the --expect-existing-container argument |
DEVCONTAINER_OUTPUTS |
✗ | type=image,name=[DEVCONTAINER_REGISTRY],push=[DEVCONTAINER_PUSH_IMAGE] |
Each output arg to apply to the image build, space separated |
DEVCONTAINER_PREBUILD_SCRIPT |
✗ | None | The path to a script to execute in advance of image build operations, e.g. for docker login s (see Initialize script prebuild file usage). Relative to the repo root; must resolve to a file within the repo directory |
DEVCONTAINER_PUSH_IMAGE |
✗ | false |
Whether to push the image to the provided registry (requires DEVCONTAINER_REGISTRY ) |
DEVCONTAINER_REGISTRY |
✗ | DEVCONTAINER_IMAGE |
The registry for the image and/or cache |
* |
✗ | N/A | In the case of bake builds, all additional environment variables are forwarded to the build execution context |
The image build leverages any values provided or computed to DEVCONTAINER_CACHE_FROM
as cache inputs.
The devcontainer-cache-build-initialize
script image build produces several outputs.
- An image in the local daemon image store (the
image
output type) at the name given byDEVCONTAINER_IMAGE
- Image build cache output published as specified by
DEVCONTAINER_CACHE_TO
The initialize script supports injecting a script (identified via the DEVCONTAINER_PREBUILD_SCRIPT
env var) before build actions are taken, for example to perform authentication actions such as docker login
within the initialize script container context. As the script must be available within the container context to execute, the value provided to DEVCONTAINER_PREBUILD_SCRIPT
must be a file within the repository directory, so that it may be mounted to the container.
Env vars from the host (especially useful for authenication use cases) are available to the DEVCONTAINER_PREBUILD_SCRIPT
script, but are remapped under a DEVCONTAINER_HOST_
prefix. For example, DOCKERHUB_PASSWORD
in the host would be available to the script as DEVCONTAINER_HOST_DOCKERHUB_PASSWORD
.
Configuring the devcontainer-cache-build-initialize
script with a plain image name results in targeting outputs and cache to DockerHub by default. Setting the DEVCONTAINER_REGISTRY
to ghcr.io/[your user/org name]
instead allows you to target the GitHub container registry instead. To set up a container repository for this in a local environment, use docker login
against ghcr.io
. For GitHub Codespaces environments, use the following steps:
- Create a Personal Access Token with
write:packages
scope for the repository to which the image belongs. - Add the token as a Codespace secret, to the repository to which the Codespaces environment belongs.
- Name the variable for the token secret as
[prefix for your repo]_CONTAINER_REGISTRY_PASSWORD
- Create another secret with the same name prefix but for the username
[prefix for your repo]_CONTAINER_REGISTRY_USER
, with the value set to your GitHub username - Create another secret with the same name prefix called
[prefix for your repo]_CONTAINER_REGISTRY_SERVER
, with the value set toghcr.io
These are automatically applied by GitHub to authenticate in a Codespace. This is necessary because other secrets are not accessible during Codespace image build.
If using GHCR as the DEVCONTAINER_REGISTRY
, the addition of any layer to the devcontainer definition involves configuring a registry for it with appropriate access. This is best achieved by leveraging the GitHub Actions workflow, as registry configuration is established automatically. To do this, simply open a PR that contains the layer addition, before trying to rebuild a devcontainer/Codespace with the layer addition. Otherwise, registries may be created as private and require a manual settings change to make them public.
Leveraging the entire lifecycle of the devcontainer cache requires applying a CI/CD workflow to prepopulate cache. This may be achieved via the reusable workflow defined in .github/workflows/devcontainer-cache-build.yaml
, e.g.:
on: push
jobs:
devcontainer-cache-build:
uses: rcwbr/devcontainer-cache-build/.github/workflows/[email protected]
permissions:
packages: write
The default behavior of the workflow provides arguments for use with the useradd Dockerfile partial for Codespaces user provisioning. These arguments must be forwarded to the devcontainer-cache-build-initialize
script, e.g. via the DEVCONTAINER_BUILD_ADDITIONAL_ARGS
variable:
# .devcontainer/initialize
export DEVCONTAINER_BUILD_ADDITIONAL_ARGS="$@"
curl https://raw.githubusercontent.com/rcwbr/devcontainer-cache-build/0.5.0/devcontainer-cache-build-initialize | bash
Input | Required | Default | Type | Effect |
---|---|---|---|---|
build-context-user |
✗ | "codespace" |
string | The user name to set for the .devcontainer/initialize call |
build-context-uid |
✗ | 1000 |
number | The UID to set for the .devcontainer/initialize call |
build-context-gid |
✗ | 1000 |
number | The USER_GID to set for the .devcontainer/initialize call |
devcontainer-cache-build-image-override |
✗ | "" |
string | The image name to use to override the default devcontainer-cache-build image |
initialize-args |
✗ | "" |
string | Args to provide to the devcontainer-cache-build-initialize script; by default set to values for building Codespaces-compatible images |
This repo uses the release-it-gh-workflow, with the conventional-changelog image defined at any given ref, as its automation.
It uses its own reusable devcontainer cache build workflow to pre-populate the devcontainer cache.
The GitHub repo settings for this repo are defined as code using the Probot settings GitHub App. Settings values are defined in the .github/settings.yml
file. Enabling automation of settings via this file requires installing the app.
The settings applied are as recommended in the release-it-gh-workflow usage, including tag and branch protections, GitHub App and environment authentication, and required checks.
To build the devcontainer-cache-build tool image locally, use the following command:
docker builder create --use --driver docker-container # Skip if you already have a docker-container builder activated
export IMAGE_NAME=devcontainer-cache-build
docker buildx bake -f github-cache-bake.hcl -f cwd://docker-bake.hcl https://github.com/rcwbr/dockerfile-partials.git#0.5.0