devcontainer.json Property Reference

devcontainer.json is the single source of truth for a reproducible environment. Every other page on this site configures one or two of its properties; this reference indexes them in one place so you can look up the exact key, its accepted values, and the spec-compliance rules that govern it. It targets engineers who already build containers and need the schema-level detail — precedence between properties, when a value is resolved, and which keys are mutually exclusive.

The properties below follow the DevContainer specification requirements. Where a property has its own deep-dive page, the first mention is linked. Use this page as the hub: start here, then branch into the guide that covers the property you are configuring.

Prerequisites

  • Dev Containers CLI @devcontainers/cli 0.58+ (npm i -g @devcontainers/cli) or VS Code with the Dev Containers extension
  • Docker Engine 24+ or a compatible runtime (Podman 4+, Colima)
  • A workspace containing a .devcontainer/devcontainer.json file

Validate any file on this page before committing it:

devcontainer read-configuration --workspace-folder . > /dev/null && echo "schema OK"

Property precedence and resolution order

The spec resolves configuration in a fixed order. Understanding it prevents the most common class of “my setting was ignored” bugs:

  1. Base context — exactly one of image, build.dockerfile, or dockerComposeFile establishes the container.
  2. Features — entries in features are installed on top of the base image in dependency order.
  3. Container settingscontainerEnv, mounts, runArgs, and remoteUser apply when the container starts.
  4. Lifecycle hooks — run in the sequence documented under feature and lifecycle hook sequencing.
  5. IDE layercustomizations is applied last, by the connecting client (VS Code, the CLI, or Codespaces).

Base image properties

PropertyTypeNotes
imagestringOCI reference. Pin to a SHA digest, not a floating tag — see container registry best practices. Mutually exclusive with build.
build.dockerfilestringPath to a Dockerfile relative to devcontainer.json. Mutually exclusive with image.
build.contextstringBuild context directory. Defaults to the devcontainer.json folder.
build.argsobjectBuild-time variables. Inject TARGETARCH/TARGETOS here for multi-architecture builds.
dockerComposeFilestring | string[]Delegates orchestration to Compose. Requires service and workspaceFolder. Covered in Docker Compose integration.
servicestringThe Compose service the IDE attaches to. Required with dockerComposeFile.
{
  "name": "Reference base context",
  "build": {
    "dockerfile": "Dockerfile",
    "context": "..",
    "args": { "TARGETARCH": "amd64" }
  },
  "remoteUser": "vscode"
}

Features and lifecycle properties

PropertyTypeNotes
featuresobjectMap of feature OCI references to option objects. Pin feature versions explicitly.
overrideFeatureInstallOrderstring[]Forces install order when features have implicit dependencies.
onCreateCommandstring | array | objectRuns once during the prebuild/creation phase.
updateContentCommandstring | array | objectRuns after onCreateCommand; re-runs on content updates.
postCreateCommandstring | array | objectRuns once after the container is created and the workspace is mounted.
postStartCommandstring | array | objectRuns every time the container starts.
postAttachCommandstring | array | objectRuns each time a client attaches.

Commands must be idempotent. The full ordering contract is documented in feature and lifecycle hook sequencing.

{
  "image": "mcr.microsoft.com/devcontainers/base:bookworm@sha256:2c5a...",
  "features": {
    "ghcr.io/devcontainers/features/node:1": { "version": "20" }
  },
  "postCreateCommand": "npm ci",
  "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
  "remoteUser": "vscode"
}

Runtime, mount, and environment properties

PropertyTypeNotes
remoteUserstringRequired for spec compliance. The user the IDE and lifecycle commands run as. Prevents root-owned files on mounted volumes.
containerUserstringThe user the container process starts as before remoteUser is applied.
containerEnvobjectEnvironment variables baked into the container at start.
remoteEnvobjectVariables injected into the IDE/remote session only; supports ${localEnv:VAR}.
mountsarrayBind or volume mounts. Use named volumes for caches — see npm cache fixes.
runArgsstring[]Raw arguments passed to docker run (e.g. --cap-add, --memory).
forwardPorts(number|string)[]Ports auto-forwarded to the host.
portsAttributesobjectPer-port labels, protocols, and onAutoForward behaviour.
{
  "image": "mcr.microsoft.com/devcontainers/base:bookworm@sha256:2c5a...",
  "containerEnv": { "TZ": "UTC" },
  "remoteEnv": { "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" },
  "mounts": [
    "source=node-modules-cache,target=/workspace/node_modules,type=volume"
  ],
  "forwardPorts": [3000, 5432],
  "remoteUser": "vscode"
}

Customizations (IDE layer)

customizations is namespaced by tool. The vscode namespace is the most common; it is applied by the VS Code DevContainer extension after the container is running.

KeyTypeNotes
customizations.vscode.extensionsstring[]Extension IDs installed in the container’s server.
customizations.vscode.settingsobjectWorkspace settings scoped to the container.
customizations.codespacesobjectCodespaces-only settings such as openFiles.

Validation & Testing

Confirm the resolved configuration matches your intent — read-configuration prints the merged result after features and variable substitution:

# Print the fully merged configuration the runtime will use
devcontainer read-configuration --workspace-folder . --include-merged-configuration | jq '.mergedConfiguration | keys'

# Build without an IDE to confirm the file is runnable headlessly
devcontainer up --workspace-folder . --remove-existing-container

For CI integration, drive the same commands through the devcontainer CLI for headless environments.

Common Pitfalls

SymptomRoot CauseRemediation
customizations.vscode.settings ignoredSettings nested directly under vscode instead of vscode.settingsMove keys into the settings object inside the vscode namespace
Root-owned files in the workspaceremoteUser omitted, commands ran as rootDeclare remoteUser and ensure the user exists in the base image
Feature installed before its dependencyImplicit ordering not honouredSet overrideFeatureInstallOrder to pin the sequence
${localEnv:VAR} resolves emptyVariable not exported in the host shell that launched the IDEExport the variable before launch, or use remoteEnv defaults
Both image and build definedMutually exclusive base contextsKeep exactly one base-context property

Conclusion

Treat devcontainer.json as a precedence machine: base context first, features next, runtime settings, lifecycle hooks, then the IDE layer last. If a value seems ignored, find where it sits in that order and which later layer overrode it. Declare remoteUser on every file and pin every reference, and the resolved configuration becomes deterministic across machines.

FAQ

Can I use both image and build in the same file? No. They are mutually exclusive base contexts. Use image for prebuilt references and build when you maintain a Dockerfile; if you need both a registry base and customizations, put the registry image in your Dockerfile’s FROM line.

Where does remoteEnv differ from containerEnv? containerEnv is set on the container process itself and visible to every process. remoteEnv is injected only into the IDE/remote session and supports host-side substitution such as ${localEnv:GITHUB_TOKEN}, making it the right place for per-developer secrets.

How do I see the configuration after features and variables are merged? Run devcontainer read-configuration --include-merged-configuration. It prints the effective configuration the runtime uses, which is the only reliable way to debug precedence questions.