Injecting Secrets into a DevContainer Safely
A secret baked into an image layer or committed to .devcontainer/ is recoverable forever. This page injects secrets at runtime instead — via remoteEnv pulling from the host or Codespaces secret store — so credentials never touch an image layer or git history.
Prerequisites
You need a secret store and a devcontainer you can add remoteEnv to.
- A host env var or Codespaces/host secret store holding the secret.
- A devcontainer config you can edit.
- No secrets currently committed (rotate any that are).
Step-by-Step Implementation
- Store the secret outside the repo (host env or secret store).
export NPM_TOKEN=... # or set it in the Codespaces secret store
- Reference it via remoteEnv — never a literal value.
{
"remoteEnv": { "NPM_TOKEN": "${localEnv:NPM_TOKEN}" },
"remoteUser": "vscode"
}
- Consume it at runtime, e.g. in a hook.
{ "postCreateCommand": "npm ci" }
- Verify the secret is not baked into the image.
docker history --no-trunc <image> | grep -i NPM_TOKEN || echo "clean"
Common Pitfalls
Secret leaks come from baking values into images, configs, or Dockerfile ENV.
| Symptom | Root Cause | Remediation |
|---|---|---|
| Token found in image layers | Baked via Dockerfile ENV/ARG | Inject via remoteEnv at runtime; rotate |
| Secret committed to .devcontainer | Literal value in config | Reference ${localEnv:...} from a store |
| Secret leaks in CI logs | Echoed by a command | Mask it; don't print secret env |
| Long-lived token over-exposed | Static broad-scope credential | Use short-lived scoped tokens |
Conclusion
Keep secrets out of everything that persists. Store them in the host or Codespaces secret store and inject at runtime with remoteEnv referencing ${localEnv:…}, so no image layer or committed file ever holds the value. Prefer short-lived, scoped tokens to bound any leak.
FAQ
Why not use a Dockerfile ARG/ENV for a token?
Because it persists in the image's build history and layers, recoverable with docker history even after you remove it. Inject secrets at runtime via remoteEnv from a store instead, so nothing sensitive is ever written into an image.
How does remoteEnv keep the secret out of the repo?
remoteEnv references an environment variable (for example ${localEnv:NPM_TOKEN}) resolved at attach time from your host or the Codespaces secret store. The config stores only the name, never the value, so the repo stays clean.
What if a secret was already committed? Rotate it immediately — assume it is compromised — then remove it from history and switch to runtime injection. A committed secret remains in git history until rewritten, so rotation is the real fix; scrubbing history is secondary.
Related
- Up to DevContainer Security & Secrets Management — the overview of hardening.
- Running DevContainers as a Non-Root User — least-privilege execution.
- Scanning DevContainer Images for Vulnerabilities — catching baked secrets and CVEs.