Skip to content

Devcontainer

Lifecycle + shell against a project's local .devcontainer/ stack, and scaffolding new devcontainer configs from the flavour templates published to the local OCI registry. Registry, base image, templates and feature each have their own submodule (just devcontainer registry|publish); all build-time sources live under ~/.just/devcontainer/.framework/.

All lifecycle recipes are [no-cd] — run them from the project root so the devcontainer CLI finds .devcontainer/. See just devcontainer for the recipe list.

Day-to-day workflow

Open a project's devcontainer:

cd ~/project && just devcontainer run

run chains build → up → shell. The build is a no-op when the image is cached, so re-running is cheap.

Already running, want a second shell:

just devcontainer shell        # zsh in the running container
just devcontainer shell bash   # bash instead

Run a one-off command:

just devcontainer exec just lint
just devcontainer exec mvn -pl foo test

Tearing down

The devcontainer CLI intentionally has no down recipe. To stop and remove the stack, switch modules:

just docker compose down

Run it from the project root — same .devcontainer/docker-compose.yml the lifecycle recipes target.

Scaffolding a new devcontainer

scaffold republishes the flavour templates to the local registry, applies the chosen flavour into your project via devcontainer templates apply (substituting the project name), and runs the flavour's init script. Because scaffold republishes first, edits to the template source under ~/.just/devcontainer/.framework/ are always live — there is no separate publish step to forget.

cd ~/new-project
just devcontainer scaffold spring-cloud-gcp   # name the flavour
just devcontainer scaffold -i                 # pick via fzf

just devcontainer flavours enumerates what's available. DEVCONTAINER_TARGET overrides the target directory (defaults to where you invoked just). --variant/-v <tag> scaffolds against a non-default base image variant — publish it first (just devcontainer publish image -v <tag>), or the container build will fail pulling it.

Re-running scaffold in an existing project is how you pick up framework updates: template-owned files (.devcontainer/, Justfile, help.md, submodule .just files) are overwritten without further warning; everything else — your code, the init-script artefacts — is left alone. The confirmation prompt lists what's already in the target; read it before typing y.

Registry, base image, features

Scaffolded projects pull two things from the local OCI registry at build time: the pre-baked base image and the dotfiles-base feature (SSH agent forwarding, chezmoi refresh, docker + just). registry up starts the registry (idempotent), publish image builds and pushes the base image (-v <tag> for a non-default variant), publish features publishes the feature. registry ls shows what's currently published — base image tags plus every template and feature with its version and description — and the registry serves a web UI at its root (http://localhost:5001) for browsing the same. Cleanup is automatic: retention policies in the registry config prune superseded template/feature versions and garbage-collect their blobs; there is nothing to run.

publish features is deliberate, not automatic: a new feature version invalidates the feature layer in every project's build cache, so the next rebuild of each project redoes those layers. Run it when you actually change the base plumbing under ~/.just/devcontainer/.framework/features/.

If a first build/up fails pulling localhost:5001/..., run just devcontainer registry up, publish image, and publish features first.

Gotchas

  • Run from the project root. All lifecycle recipes are [no-cd]. From elsewhere, the devcontainer CLI won't find your .devcontainer/ directory and the recipe will fail.
  • run requires the image to build cleanly. First-time builds can be slow and surface project-specific Docker errors. If run fails, try just devcontainer build to isolate the build from the start phase.
  • scaffold mutates the current directory. It writes .devcontainer/, a Justfile, and may write project init artefacts — and it overwrites template-owned files without per-file prompts. Run it in a fresh project directory or commit before invoking.
  • scaffold needs the docker daemon. It republishes templates to the local registry before applying; with docker down it fails at the registry bring-up, not at file-copying.
  • scaffold -i requires fzf. Without it, pass the flavour name as a positional argument.
  • The registry must allow insecure localhost:5001. See the header comment in the module's .framework/registry/docker-compose.yml for the colima configuration.