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/cli0.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.jsonfile
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:
- Base context — exactly one of
image,build.dockerfile, ordockerComposeFileestablishes the container. - Features — entries in
featuresare installed on top of the base image in dependency order. - Container settings —
containerEnv,mounts,runArgs, andremoteUserapply when the container starts. - Lifecycle hooks — run in the sequence documented under feature and lifecycle hook sequencing.
- IDE layer —
customizationsis applied last, by the connecting client (VS Code, the CLI, or Codespaces).
Base image properties
| Property | Type | Notes |
|---|---|---|
image | string | OCI reference. Pin to a SHA digest, not a floating tag — see container registry best practices. Mutually exclusive with build. |
build.dockerfile | string | Path to a Dockerfile relative to devcontainer.json. Mutually exclusive with image. |
build.context | string | Build context directory. Defaults to the devcontainer.json folder. |
build.args | object | Build-time variables. Inject TARGETARCH/TARGETOS here for multi-architecture builds. |
dockerComposeFile | string | string[] | Delegates orchestration to Compose. Requires service and workspaceFolder. Covered in Docker Compose integration. |
service | string | The 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
| Property | Type | Notes |
|---|---|---|
features | object | Map of feature OCI references to option objects. Pin feature versions explicitly. |
overrideFeatureInstallOrder | string[] | Forces install order when features have implicit dependencies. |
onCreateCommand | string | array | object | Runs once during the prebuild/creation phase. |
updateContentCommand | string | array | object | Runs after onCreateCommand; re-runs on content updates. |
postCreateCommand | string | array | object | Runs once after the container is created and the workspace is mounted. |
postStartCommand | string | array | object | Runs every time the container starts. |
postAttachCommand | string | array | object | Runs 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
| Property | Type | Notes |
|---|---|---|
remoteUser | string | Required for spec compliance. The user the IDE and lifecycle commands run as. Prevents root-owned files on mounted volumes. |
containerUser | string | The user the container process starts as before remoteUser is applied. |
containerEnv | object | Environment variables baked into the container at start. |
remoteEnv | object | Variables injected into the IDE/remote session only; supports ${localEnv:VAR}. |
mounts | array | Bind or volume mounts. Use named volumes for caches — see npm cache fixes. |
runArgs | string[] | Raw arguments passed to docker run (e.g. --cap-add, --memory). |
forwardPorts | (number|string)[] | Ports auto-forwarded to the host. |
portsAttributes | object | Per-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.
| Key | Type | Notes |
|---|---|---|
customizations.vscode.extensions | string[] | Extension IDs installed in the container’s server. |
customizations.vscode.settings | object | Workspace settings scoped to the container. |
customizations.codespaces | object | Codespaces-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
| Symptom | Root Cause | Remediation |
|---|---|---|
customizations.vscode.settings ignored | Settings nested directly under vscode instead of vscode.settings | Move keys into the settings object inside the vscode namespace |
| Root-owned files in the workspace | remoteUser omitted, commands ran as root | Declare remoteUser and ensure the user exists in the base image |
| Feature installed before its dependency | Implicit ordering not honoured | Set overrideFeatureInstallOrder to pin the sequence |
${localEnv:VAR} resolves empty | Variable not exported in the host shell that launched the IDE | Export the variable before launch, or use remoteEnv defaults |
Both image and build defined | Mutually exclusive base contexts | Keep 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.
Related
- Understanding the DevContainer Specification — the schema and version migration path behind these properties.
- Feature & Lifecycle Hook Sequencing — the exact order hooks execute in.
- Container Registry Best Practices for Dev Images — how to pin the
imageproperty safely. - DevContainer Architecture & Core Tooling — the parent pillar tying these properties into one architecture.