| marp | true |
|---|---|
| footer | @Chris_L_Ayers - https://chris-ayers.com |
| theme | custom-default |
BlueSky: @chris-ayers.com
LinkedIn: - chris-l-ayers
Blog: https://chris-ayers.com/
GitHub: Codebytes
Mastodon: @Chrisayers@hachyderm.io
Twitter: @Chris_L_Ayers
- Why Dev Containers
- What They Are & How They Work
- Quick Start & Building
- Templates vs Features vs Customizations
- Core Concepts (Image vs Feature vs Template)
- GitHub Codespaces
- Demo
- Advanced: Security, Multi-Service, Prebuilds, Performance & Cost, Debug & Ports, Troubleshooting, Limits
- Resources & Q&A
- Onboard new contributors quickly
- Consistent tooling and versions for all developers
- Reduce system conflicts and "works on my machine" issues
- Secure, isolated environments for development
- Easy startup tasks and reproducible builds
- Containerized environments for development
- Let you open any folder in VS Code with all dependencies ready
- Run apps, tools, or runtimes needed for a project
- Based on the open containers.dev specification
- VS Code (Dev Containers extension, Codespaces extension)
- Docker (local or remote)
- GitHub account (for Codespaces)
- Optionally: IntelliJ IDEA, other containers CLIs like podman
Your editor talks to a containerized environment with all dependencies, keeping your local system clean.
From the Command Palette:
- Templates: project bootstrap (scaffold config)
- Features: add tools/runtimes (idempotent, versioned)
- Customizations: editor settings, extensions, startup tasks, port forwarding
| Concept | When to Use | Change Frequency | Pinning Strategy |
|---|---|---|---|
| Base Image | Need different language/runtime base or OS | Low | Tag (e.g. :1-20-bookworm) or digest |
| Feature | Add/upgrade a CLI (gh, kubectl), language, or utility | Medium | Major tag (node:1) or minor for guarantees |
| Template | Starting a new repo / adding devcontainer from scratch | One-off | Template version (commit / release) |
Principles:
- Keep Dockerfile minimal; use Features for add-ons
- Templates are for initial setup, not ongoing changes
- Prebuild images for slow, shared setup (language servers, SDKs)
Practices:
- Everyone needs it & rarely changes? → Bake into image/Feature
- Project-scoped & changes often? → postCreateCommand
- Personal? → Dotfiles/Settings Sync
- Add via customizations.vscode.extensions
- Opt-out: prefix with '-' to remove inherited extension
- Default extensions (user setting): dev.containers.defaultExtensions
- Force location (rare): remote.extensionKind overrides
- Keep lean: only language / tooling essentials; reduces startup & index time
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-docker",
"-dbaeumer.vscode-eslint" // opt-out inherited
]
}
}- Auto port detection: output parsing ("Listening on 3000") triggers forward suggestion
- forwardPorts: auto-forward on start; portsAttributes: label, onAutoForward, visibility
- Published vs Forwarded:
- Forwarded: appears as localhost to app (ideal for dev auth flows)
- Published: network-accessible (team demos, external callbacks)
- Browser preview & automatic HTTPS upgrade (if dev server supports)
"portsAttributes": {
"3000": { "label": "Web UI", "onAutoForward": "openBrowser" },
"9229": { "label": "Node Inspector", "elevateIfNeeded": true }
}{
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/node:1": { "version": "20" }
},
"forwardPorts": [3000],
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint", "ms-azuretools.vscode-docker"],
"settings": { "editor.formatOnSave": true }
}
},
"postCreateCommand": "npm ci",
"remoteUser": "node"
}Set environment variables in devcontainer.json:
{
"containerEnv": { "MY_ENV_VAR": "value" },
"remoteEnv": { "API_TOKEN": "${localEnv:API_TOKEN}" }
}containerEnv: for all processes in the containerremoteEnv: for the VS Code server and extensions
- containerEnv: set at container start (Dockerfile, Compose, or devcontainer.json)
- remoteEnv: injected at attach time (user, secrets, paths)
- Use dynamic values:
${localEnv:VAR}or${containerWorkspaceFolder} userEnvProbe: merge shell profile exports into remoteEnv ("loginShell", "interactiveShell", or "none")
You can mount local folders or files into your dev container using the mounts property:
{
"mounts": [
"source=${localWorkspaceFolder}/.npmrc,target=/root/.npmrc,type=bind,consistency=cached",
"source=${env:HOME}/.ssh,target=/root/.ssh,type=bind,consistency=cached"
]
}This is useful for sharing config files, SSH keys, or other resources from your host.
Always run as a non-root user for security. Most official images set remoteUser to vscode or devcontainer by default.
To enforce non-root:
{
"remoteUser": "vscode"
}Or add the non-root-user Feature if needed.
More: Add a non-root user
- Cloud-hosted, instant dev environments for any repo
- No local Docker or setup required
- Consistent, secure, and disposable workspaces
- Fast onboarding: prebuilds, dotfiles, and settings sync
- Start a dev environment from any branch, PR, or template
- Prebuilds: dependencies and tools ready before you connect
- Port forwarding, shared terminals, and Live Share for collaboration
- Personalization: dotfiles, settings sync, user secrets
- Open any repo in a codespace from GitHub.com, VS Code, or CLI
- Choose machine size, region, and devcontainer config
- Suspend, resume, or delete codespaces as needed
- Works with VS Code (browser/desktop), JetBrains Gateway, and GitHub CLI
- Linux-only (x86_64); no Windows containers
- Single container unless using Compose
- Bind mounts slower on macOS/Windows (prefer volumes)
- Pay for compute, storage, and prebuilds (beyond free quota)
- See docs.github.com/codespaces for details
- Pay for compute hours, storage, and prebuild usage (beyond free quota)
- Key levers: machine size, idle timeout, auto-delete unused codespaces
- Strategies: right-size, prebuild only critical branches, monitor usage/cost
- Always check current docs for pricing details
The devcontainer CLI lets you build, run, and automate dev containers from the terminal—no VS Code required.
devcontainer build --workspace-folder .Build a dev container imagedevcontainer up --workspace-folder .Start a dev containerdevcontainer exec --workspace-folder . bash -lc "npm test"Run a command inside
devcontainer.json (simplified):
{
"image": "ghcr.io/your-org/your-prebuilt:latest",
"postAttachCommand": "npm run dev"
}name: Prebuild Dev Container
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: devcontainers/ci@v0.3
with:
imageName: ghcr.io/your-org/your-prebuilt
push: true
runCmd: |
npm ci
npm test --if-presentOn Windows/WSL, you may see many modified files due to line ending differences between host and container.
- Add a
.gitattributesfile to enforce consistent line endings:
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf BlueSky: @chris-ayers.com
LinkedIn: - chris-l-ayers
Blog: https://chris-ayers.com/
GitHub: Codebytes
Mastodon: @Chrisayers@hachyderm.io
Twitter: @Chris_L_Ayers






