Architecture specification

Development Container

The authoritative development environment for a repository — reusable features provide the toolchains, and repository-specific lifecycle hooks finish the workspace.

Applies at
.devcontainer/
Status
Stable
Source · managed
.devcontainer/README.md

01 Defines

A container definition, following the Development Containers specification, that provisions every tool, runtime, service, and utility needed to work on the repository, so a fresh rebuild is ready for development without manual bootstrapping.

02 Applies when

  • A repository needs the same toolchain for every developer, editor, and automation surface.
  • Tools and services must be provisioned before any project command runs.
  • Common tooling can be expressed as reusable devcontainer features rather than hand-written installs.

03 Boundaries

  • Provisioning the environment is in scope; project operations such as restore, generate, build, and test are not.
  • Only tooling the repository owns or directly uses is provisioned; platform integration tooling only where that boundary is documented.
  • The configuration does not depend on one hosted editor or source-control provider.

Expected behaviour

  1. 01
    devcontainer.json declares settings, mounts, features, and one lifecycle command per phase.
  2. 02
    devcontainer-lock.json records the resolved version and digest of every feature.
  3. 03
    The Dockerfile stays minimal and delegates toolchain installation to features.
  4. 04
    Reusable toolchains come from devcontainer features; repository-specific setup comes from lifecycle scripts.
  5. 05
    Every lifecycle command delegates to the shared run-parts.sh runner.
  6. 06
    Any tool that can run the Dev Containers CLI can build and use the environment.

Behaviour#

The development container is responsible for the environment; the repository’s commands only use it. Developers are not expected to run a separate bootstrap step to install tools after the container is built — if a tool is needed, the container provides it.

The definition is layered. A small base image sets the operating system and primary runtime. Devcontainer features add reusable capabilities on top. The Devcontainer Lifecycle adds the last, repository-specific layer.

FlowLayers of the environment
  1. 01 Base image Dockerfile — one FROM line, kept minimal
  2. 02 Features Toolchains, CLIs, and services, pinned by devcontainer-lock.json
  3. 03 Container Mounts, remoteUser, containerEnv, and editor customisations
  4. 04 Lifecycle Repository-specific hooks run by run-parts.sh Devcontainer Lifecycle
  • .devcontainer/
  • devcontainer.json Settings, mounts, features, lifecycle commands
  • devcontainer-lock.json Resolved feature versions and digests
  • Dockerfile Base image; direct customisation kept minimal
  • lifecycle/ Shared runner and hook directories Devcontainer Lifecycle

Features first#

A devcontainer feature is the preferred way to install a common toolchain, configure a runtime dependency, or expose a development service. When a suitable upstream feature exists, it is used instead of re-implementing the install in a lifecycle script or the Dockerfile.

When no upstream feature fits, or organisation-specific behaviour is required, a local feature may be developed under ./features. It stays generic enough to be extracted and reused by other repositories later. Workspace initialisation, shell customisation, and other repository-specific behaviour stay out of features and belong in lifecycle scripts.

Tool versions may come from shared metadata such as .tool-versions, or be pinned directly in devcontainer or CI configuration. Where consistency matters, the pins are shared or coordinated.

Examples#

Every lifecycle command in this repository delegates to the same runner, and toolchains arrive as features:

.devcontainer/devcontainer.json (excerpt)
{
  "build": { "dockerfile": "Dockerfile" },
  "initializeCommand": "bash .devcontainer/lifecycle/run-parts.sh initialize",
  "postStartCommand": "bash .devcontainer/lifecycle/run-parts.sh post-start",
  "features": {
    "ghcr.io/devcontainers/features/aws-cli:1": {},
    "ghcr.io/devcontainers/features/terraform:1": {},
    "ghcr.io/devcontainers/features/hugo:1": {},
    "ghcr.io/devcontainers/features/docker-in-docker:2": { "moby": true }
  }
}

The environment is driven entirely from the Dev Containers CLI, so any editor or automation surface can wrap the same commands:

devcontainer up --workspace-folder .                  # Build and start
devcontainer exec --workspace-folder . make doctor    # Verify the environment
devcontainer exec --workspace-folder . make validate  # Run project validation
devcontainer build --workspace-folder .               # Prebuild or warm the cache

make doctor is the Environment Doctor : it reports what the container was expected to provide and what is missing.