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.json metadata file.
  • A repo/registry to host the Feature.

Feature prerequisitesYou need an install script, metadata, a reference, and a test.install.shthe logicfeature.jsonmetadata + optionsReferencein featuresTestinstall works

Step-by-Step Implementation

  1. 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
  1. Describe it with metadata, including options.
{
  "id": "mytool",
  "version": "1.0.0",
  "options": { "version": { "type": "string", "default": "latest" } }
}
  1. Reference the Feature from a devcontainer.
{ "features": { "./features/mytool": { "version": "1.2.0" } }, "remoteUser": "vscode" }
  1. Test that it installs in a fresh build.

Feature anatomyA Feature is an install script plus metadata whose options become script env vars.install.shruns at build, as rootdevcontainer-feature.jsonid, version, optionsOptions -> envpassed to the scriptReferencedin the features object

Common Pitfalls

Feature bugs are non-idempotent installs or missing option handling.

Feature triageA triage path from a broken Feature to a reusable, idempotent one.Does re-running the install duplicatestate?YESMake install.sh idempotentAre options read from env?YESMetadata matches the scriptReusable, correct Feature

SymptomRoot CauseRemediation
Feature install not reproducibleNon-idempotent scriptGuard installs; pin the downloaded version
Option ignoredNot read from the env varRead the uppercased option name from env
Installs in the wrong orderNo installsAfterDeclare ordering in the metadata
Works locally, not when publishedRelative asset pathsBundle 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.

Feature payoffPackaging install logic as a Feature gives one reference and consistent, ordered installs.A Feature hasinstall.sh (as root)metadata + optionsdeclared orderingSo you getOne referenceConsistent installsOrdered composition

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.