agentleFS
DeepSpaceCartelDeepSpaceCartel/skills/skills/devcontainer/SKILL.md

devcontainer

Skill2 starsChanged 2 months agoMITView on GitHub

How to write, review, and secure Dev Container configuration per the Development Container Specification (https://containers.dev/, https://containers.dev/implementors/json_reference/) - project-agnostic. Covers devcontainer.json (locations, every property, image/Dockerfile/Compose scenarios, lifecycle scripts, variables, ports, customizations); Features (devcontainer-feature.json, options-to-env-vars, dependsOn/installsAfter ordering); finding and vetting trustworthy Features (the containers.dev index, trust tiers, auditing install.sh and inherited privileges, pinning, lockfile, Dependabot); authoring, testing and publishing Features (devcontainer features test, devcontainers/action); Templates; OCI distribution; and image metadata merging. Use when writing or reviewing a devcontainer.json, choosing or auditing a third-party Feature, building or publishing a Feature or Template, debugging install order or options, or setting up a reproducible dev environment for VS Code, Codespaces, DevPod, or similar.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

Files
SKILL.md

Dev Containers

The Development Container Specification defines metadata — chiefly a devcontainer.json file — that enriches an OCI container with what's needed to develop inside it: how to build or reach the container, environment variables, mounts, lifecycle commands, and tool-specific customizations. Full spec at containers.dev; the implementor-facing detail this skill is built from lives at containers.dev/implementors/spec and containers.dev/implementors/json_reference. Reference implementation: devcontainers/cli. Spec source/history: github.com/devcontainers/spec.

Three things share this one spec, and most confusion is about telling them apart:

  • devcontainer.json — describes one environment directly (this file's properties — see references/json-reference.md).
  • Dev Container Features — self-contained, shareable install code + config, referenced by ID under features — see references/features.md.
  • Dev Container Templates — a starter .devcontainer/ folder + options, dropped into a new or existing project — see references/templates.md.

The core idea

A dev container is described by scenario (image, Dockerfile, or Docker Compose) plus a common set of properties layered on top. Exactly one scenario applies, and each has its own required property:

ScenarioRequired propertyNotes
ImageimagePull-only, no build step.
Dockerfilebuild.dockerfilePath relative to devcontainer.json. build.context/args/options/target/cacheFrom control the build.
Docker ComposedockerComposeFile + serviceservice names the main container all tooling attaches to. image/Dockerfile properties aren't needed — Compose already has them.

Everything else — features, lifecycle scripts, remoteEnv, mounts, customizations, etc. — applies on top of whichever scenario is chosen. The full property table is in references/json-reference.md.

Quick example

// .devcontainer/devcontainer.json
{
  "name": "My Project",
  "image": "mcr.microsoft.com/devcontainers/base:bookworm",
  "features": {
    "ghcr.io/devcontainers/features/node:1": { "version": "20" },
    "ghcr.io/devcontainers/features/github-cli": {}
  },
  "forwardPorts": [3000],
  "postCreateCommand": "npm install",
  "customizations": {
    "vscode": { "extensions": ["dbaeumer.vscode-eslint"] }
  }
}

devcontainer.json (JSON-with-Comments) is looked up, in order of precedence, at .devcontainer/devcontainer.json, .devcontainer.json, or one level down at .devcontainer/<folder>/devcontainer.json — a tool should let the user pick when more than one exists.

Where to look

ReferenceCovers
references/json-reference.mdEvery devcontainer.json property: general + image/Dockerfile-specific + Compose-specific, lifecycle scripts (string/array/object forms, waitFor), port attributes, hostRequirements (incl. gpu), ${...} variable substitution, the declarative secrets property
references/features.mddevcontainer-feature.json authoring, the options → env var contract, referencing a Feature (OCI/tarball/local), install ordering (dependsOn, installsAfter, overrideFeatureInstallOrder), Feature lifecycle hooks, ${devcontainerId}, renaming/deprecating a Feature
references/using-features.mdFinding Features and deciding which to trust: the containers.dev index (listing ≠ review), trust tiers, vetting install.sh and inherited privileged/capAdd/mounts, pinning tags vs version options, lockfile + devcontainer upgrade/outdated, Dependabot, vendoring, the reference collection at a glance, docker-in-docker vs docker-outside-of-docker
references/feature-authoring.mdWriting a robust install.sh (distro/arch detection, idempotency, _REMOTE_USER, verifying downloads), devcontainer features test (autogenerated/scenario/duplicate/global), publishing with devcontainers/action, getting listed on containers.dev
references/templates.mddevcontainer-template.json authoring, the options string-replacement model, optionalPaths, referencing a Template
references/distribution.mdPackaging Features/Templates as tarballs, OCI registry distribution, devcontainer-collection.json, locally-referenced Features, the devcontainer-lock.json lockfile
references/lifecycle-and-tools.mdThe environment lifecycle (init → create → post-create → stop/resume), image metadata (devcontainer.metadata label) merge logic, users, environment variable classes, mounts, and the customizations namespace pattern (VS Code, Codespaces, secrets)

Gotchas

  • features values aren't always objects. A bare string is shorthand for {"version": "<string>"} — "go": "1.18" and "go": {"version": "1.18"} are equivalent. An empty object {} means "use this Feature's defaults."
  • A Feature is root-level build code plus runtime flags you inherit. Being listed on containers.dev is not a review. Prefer ghcr.io/devcontainers/features/*, then vendor-published collections, and read install.sh and the manifest (privileged, capAdd, host mounts) before adopting anything else — see references/using-features.md.
  • A Feature/Template reference without a version tag implicitly means :latest. Pin (:1, :1.2.3, or @sha256:...) for reproducibility — see the lockfile.
  • dependsOn and installsAfter are not interchangeable. dependsOn is recursive and adds the dependency Feature to the install set even if the user never referenced it. installsAfter only reorders Features already queued for install and is not recursive; a Feature named there that nobody asked for is silently ignored, not pulled in.
  • String vs. array vs. object changes execution, not just syntax, for every lifecycle command and postAttachCommand/etc.: a string goes through /bin/sh (so &&, quoting, and shell expansion apply); an array runs the executable directly with no shell (quotes in arguments are passed through literally, not stripped); an object runs each of its values in parallel. See references/json-reference.md.
  • containerEnv vs. remoteEnv is a rebuild-cost tradeoff, not a synonym pair. containerEnv is baked into the container (visible to every process, including the entrypoint) and needs a rebuild to change. remoteEnv is applied by the supporting tool to the processes it starts (terminals, tasks, devcontainer exec) and can change without a rebuild — but isn't visible to the container's own entrypoint. ${containerEnv:VAR} can be read from within a remoteEnv value to extend rather than replace (e.g. appending to PATH).
  • workspaceMount requires workspaceFolder (and vice versa) for image/Dockerfile scenarios — they're set as a pair, not independently. Docker Compose scenarios use the Compose file's own volumes/mounts instead of workspaceMount.
  • forwardPorts and appPort solve different problems. forwardPorts makes a port reachable from the host without the app needing to listen on anything but localhost inside the container. appPort publishes the port (Docker -p), which requires the app to listen on 0.0.0.0 — and published ports don't behave like localhost to an app that only trusts loopback callers.
  • Image metadata merge order matters for "last value wins" properties (remoteUser, userEnvProbe, shutdownAction, …): the devcontainer.json on disk is always applied last, after every Feature's metadata — a user's local file wins conflicts with any Feature, always.
  • Feature/Template IDs are case-insensitive but should be authored and compared as lowercase. Don't rely on casing to distinguish two references — normalize before comparing.
  • The secrets property in devcontainer.json only declares which secrets a project wants (name + description + docs URL) — it never holds a secret's value. Actual values are supplied out-of-band by the supporting tool (a secrets file, OS keychain, cloud secret store). Don't treat a documented secrets block as a place to put real credentials, and don't treat remoteEnv as secret-safe just because it isn't baked into the image.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.