Writing a Custom DevContainer Feature
When your team installs the same internal tool in every devcontainer by copy-pasting shell into postCreateCommand, a custom Feature is the fix. This page authors a reusable Feature — an install script plus devcontainer-feature.json metadata — so the tool installs consistently, in the right order, and by a single reference.
Prerequisites
You need a tool install to package and a place to host the Feature.
- A shell install script for the tool.
- A
devcontainer-feature.jsonmetadata file. - A repo/registry to host the Feature.
Step-by-Step Implementation
- Write the install script. It runs during the build as root.
#!/usr/bin/env bash
set -e
echo "Installing mytool ${VERSION}"
curl -fsSL "https://example.com/mytool-${VERSION}" -o /usr/local/bin/mytool
chmod +x /usr/local/bin/mytool
- Describe it with metadata, including options.
{
"id": "mytool",
"version": "1.0.0",
"options": { "version": { "type": "string", "default": "latest" } }
}
- Reference the Feature from a devcontainer.
{ "features": { "./features/mytool": { "version": "1.2.0" } }, "remoteUser": "vscode" }
- Test that it installs in a fresh build.
Common Pitfalls
Feature bugs are non-idempotent installs or missing option handling.
| Symptom | Root Cause | Remediation |
|---|---|---|
| Feature install not reproducible | Non-idempotent script | Guard installs; pin the downloaded version |
| Option ignored | Not read from the env var | Read the uppercased option name from env |
| Installs in the wrong order | No installsAfter | Declare ordering in the metadata |
| Works locally, not when published | Relative asset paths | Bundle assets with the Feature |
Conclusion
A custom Feature packages a tool install as a reusable, ordered, option-driven unit: an idempotent install.sh plus devcontainer-feature.json. Reference it once and every teammate installs the tool identically — the same discipline the official Features follow.
FAQ
When is a custom Feature worth it over a postCreateCommand?
When more than one project installs the same tool. A Feature packages the install once, installs it in a deterministic order during the build (cached as a layer), and exposes options — far better than copy-pasting shell into each project's postCreateCommand.
How do options reach my install script?
The tooling passes each option as an uppercased environment variable to install.sh. An option named version becomes $VERSION. Declare defaults and types in devcontainer-feature.json, and read the env vars in the script.
How do I control install order relative to other Features?
Declare installsAfter (or dependsOn) in the Feature metadata, or have consumers set overrideFeatureInstallOrder. This ensures your Feature runs after any it depends on, the same ordering mechanism the built-in Features use.
Related
- Up to Feature & Lifecycle Hook Sequencing — how Features install and order.
- Publishing a DevContainer Feature to GHCR — sharing your Feature.
- devcontainer.json Property Reference — the features and ordering keys.