Search across all documentation pages
devcontainer.json) and run inside a container, so every engineer gets the same toolchain.Add a .devcontainer/devcontainer.json to the repo describing the image or Dockerfile, tools, and setup commands.
Open the folder in VS Code or Cursor and choose "Reopen in Container", or use the devcontainer CLI in CI.
Keep the dev container's base aligned with your production base so the two do not drift.
A devcontainer.json built from your own Dockerfile, adding tools via features and installing dependencies on create.
{
"name": "api-dev",
"build": {
"dockerfile": "Dockerfile",
"target": "dev"
},
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {
"helm": "latest"
}
},
"forwardPorts": [3000, 5432],
"postCreateCommand": "npm ci",
"remoteUser": "node",
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint", "ms-azuretools.vscode-docker"]
}
}
}For multi-service work, point the dev container at a Compose service instead.
{
"name": "api-dev",
"dockerComposeFile": "../docker-compose.yml",
"service": "api",
"workspaceFolder": "/app",
"postCreateCommand": "npm ci"
}Now the dev container is the api service, and its depends_on databases start alongside it.
The file describes the environment: which image or Dockerfile to build, which tools to layer on, which ports to forward, and which user to run as.
remoteUser matters for parity and security. Running as node rather than root mirrors a hardened production image and keeps file ownership sane on bind mounts.
forwardPorts publishes container ports back to your host so the app and its database are reachable from the editor.
A feature is a reusable, versioned installer referenced by an OCI ref like ghcr.io/devcontainers/features/kubectl-helm-minikube:1.
Features let you add kubectl, Helm, language runtimes, or cloud CLIs without hand-writing install steps in a Dockerfile.
They compose, so the base image stays small and each team layers only what it needs.
The spec defines ordered hooks: onCreateCommand, updateContentCommand, postCreateCommand, and postStartCommand.
Use postCreateCommand for one-time setup like installing dependencies, and postStartCommand for things that must run every start.
Keeping setup in these hooks means a fresh clone becomes a working environment with zero manual steps.
The docker-outside-of-docker feature mounts the host Docker socket so commands inside the dev container talk to the host daemon.
This is a build/dev convenience. It does not change the fact that on a real cluster, containerd via the CRI runs your pods, not Docker.
devcontainer.json is an open specification with a standalone devcontainer CLI, so it is not limited to one editor.
That lets CI build and exercise the exact dev environment engineers use, closing another parity gap.
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . npm testConfusing the dev container with the production image is the big one; the dev stage ships compilers and dev dependencies you must not deploy. Build a separate runtime target for prod.
Running as root inside the dev container creates root-owned files on bind mounts, which then break host tooling; set remoteUser.
Pinning features to a floating latest reintroduces drift; pin feature versions like any other dependency.
Large postCreateCommand steps run on every rebuild of the container image; put slow, cacheable work in the Dockerfile instead so it lands in a layer.
Forwarding a port that a Compose service already binds causes conflicts; let one mechanism own each port.
Mounting the Docker socket grants broad host access, so treat it as a privileged action and avoid it on shared or untrusted machines.
Plain Dockerfile + Compose, no dev container spec. Simpler, editor-agnostic, but onboarding is manual and the editor is not preconfigured.
Nix or asdf on the host. Reproducible toolchains without a container, but they do not isolate system libraries the way a container does.
Cloud dev environments (Codespaces-style, self-hosted equivalents). Same devcontainer.json runs on a remote host, removing local resource limits at the cost of network dependence.
Language-native tooling only (virtualenv, nvm). Lightest weight, weakest isolation and parity; fine for single-language, single-service repos.
Choose dev containers when onboarding speed, polyglot toolchains, and dev-environment reproducibility matter; choose lighter options for small, uniform projects.
Do dev containers replace my production Dockerfile? No. They configure the development box. You still build and deploy a separate, hardened production image.
Can I share one config across editors? Yes. devcontainer.json is an open spec with a CLI, so VS Code, Cursor, and CI can all consume it.
How do I get kubectl and Helm inside? Add the relevant feature (for example the kubectl-helm-minikube feature) rather than scripting installs by hand.
Where do I install project dependencies? In postCreateCommand for a one-time setup, or bake truly static tooling into the Dockerfile layer for caching.
Is the mounted Docker socket safe? It is convenient but privileged; the container can control the host daemon, so avoid it on untrusted machines.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026