How to Build and Cache a Dev Container in GitHub Actions
Rebuilding a dev container every run is slow; caching layers lets CI reuse the prebuilt image and start fast.
Use the devcontainers CLI build action with a registry cache so subsequent runs pull layers instead of rebuilding.
Steps
- Set up Buildx so layer caching backends are available.
- Run
devcontainers/ciwith a cache-from/cache-to image reference. - Push the prebuilt image so other jobs and developers can reuse it.
Workflow
name: Devcontainer
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: devcontainers/ci@v0.3
with:
imageName: ghcr.io/${{ github.repository }}/devcontainer
cacheFrom: ghcr.io/${{ github.repository }}/devcontainer
push: filterNotes
- Use
push: filterso images push only on the default branch, not on every PR. - On Latchkey managed runners container builds run cheaper and self-heal if a runner drops.
Verify it actually works
A workflow that runs is not a workflow that works. Confirm the behaviour on a real event rather than on a manual dispatch, because trigger conditions, permissions, and context values all differ between the two.
# 1. validate the file before pushing
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color
# 2. trigger the real event, not workflow_dispatch
git commit --allow-empty -m "ci: verify trigger" && git push
# 3. watch it and read the conclusion, not just the colour
gh run watch
gh run view --log-failedWhat usually goes wrong first
- The workflow file must exist on the default branch before scheduled or dispatch triggers appear at all.
GITHUB_TOKENpermissions default to read-only in many organisations. Declare apermissions:block listing every scope the job needs.- Fork pull requests get a read-only token and no access to secrets, regardless of workflow configuration.
actions/checkoutgives you depth 1 on a detached HEAD, so anything needing history or a branch name needsfetch-depth: 0.