Lifecycle specification

Devcontainer Lifecycle

Six ordered hook phases that take a development container from host-side preparation to an attached, ready workspace.

Applies at
.devcontainer/lifecycle/
Status
Stable

01 Defines

The repository-specific provisioning layer of a development container: one hook directory per Dev Container lifecycle command, each run in lexical order by a shared runner.

02 Applies when

  • A repository needs workspace setup that a reusable devcontainer feature cannot provide.
  • Setup must happen at a particular moment — before the build, on creation, after content sync, on every start, or on every attach.
  • Startup checks or lightweight services must be true for every session.

03 Boundaries

  • Reusable toolchains, SDKs, and CLIs belong in devcontainer features, not lifecycle scripts.
  • Normal project operations — build, test, generate — are not environment provisioning.
  • Makefile targets and general scripts use the environment; they never silently create it.

Expected behaviour

  1. 01
    Every Dev Container lifecycle command delegates to run-parts.sh <event>, which runs the matching <event>.d/ directory.
  2. 02
    Scripts run in lexical order; numeric prefixes such as 10- and 20- make the order visible when it matters.
  3. 03
    Every script can be run again safely, and initialize.d/ is never assumed to run only once.
  4. 04
    Phases before post-create hold only the setup that later phases depend on.
  5. 05
    post-start.d/ and post-attach.d/ stay fast, because they run on every start and every attach.

Behaviour#

The image and its features provide reusable tooling. The lifecycle layer provides everything specific to this repository: linking command wrappers, configuring the shell, restoring dependencies, and running lightweight startup checks. A freshly rebuilt container, lifecycle included, should be ready for development without any manual bootstrapping.

LifecycleFrom rebuild to development session
  1. host · may repeat initializeCommand Prepare host-side files a mount or build expects
  2. once on create onCreateCommand Container-local state later phases consume
  3. on content sync updateContentCommand Restore dependencies from lockfiles and manifests
  4. once after create postCreateCommand Last-mile workspace bootstrap
  5. every start postStartCommand Fast checks and lightweight services
  6. every attach postAttachCommand Human-facing guidance and auth prompts
  • Restart containerpostStartCommand
  • Detach and reattachpostAttachCommand
  • Rebuild containerinitializeCommand

Between the hooks, the Dev Containers tooling builds the image, installs features, creates the container, and starts it. The hooks never replace those steps; they only add to them.

Directory layout#

Where setup belongs#

Devcontainer features

  • Language runtimes, SDKs, and package managers
  • CLIs and Docker-in-Docker support
  • Anything worth sharing across repositories

Lifecycle scripts

  • Linking repository-owned command wrappers
  • Shell and profile configuration for this workspace
  • Dependency restore driven by checked-in lockfiles

Examples#

Adding a hook is adding a file. This repository restores its pinned Node tooling once workspace content is available:

.devcontainer/lifecycle/update-content.d/10-install-node-dependencies.sh
#!/usr/bin/env bash
set -euo pipefail

if [ -f package-lock.json ]; then
	npm ci
else
	npm install
fi

Any phase can be run by hand through the same runner, which is useful when debugging a hook without rebuilding the container:

bash .devcontainer/lifecycle/run-parts.sh post-create
bash .devcontainer/lifecycle/run-parts.sh post-start
bash .devcontainer/lifecycle/run-parts.sh post-attach