Customizing GitHub Codespaces with Dev Containers: Dockerfile, Extensions, and Env Vars
Learn to tailor GitHub Codespaces with a devcontainer.json that references a Dockerfile, installs VS Code extensions, sets environment variables, and forwards ports. Includes a complete Node.js example, verification steps, and common pitfalls.
03 Jul 2026, 23:14 UTC

Quick answer
Define a .devcontainer/devcontainer.json that points to a Dockerfile, lists the VS Code extensions you need, sets environment variables, and forwards ports. GitHub Codespaces will build the container once, cache the layers, and give every collaborator an identical, ready‑to‑code environment.
Worked example
The following files turn a plain Node.js repository into a Codespace that includes curl, the Python extension, a custom APP_ENV variable, and port 3000 forwarded for a local server.
1. Dockerfile
# .devcontainer/Dockerfile
FROM node:18-alpine
# Install a small utility used by the project
RUN apk add --no-cache curl
# Create a non‑root user (optional but recommended)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /workspaces/$(basename $GITHUB_REPOSITORY)
2. devcontainer.json
{
"name": "Node + Python Demo",
"build": { "dockerfile": "Dockerfile" },
"extensions": [
"ms-python.python",
"dbaeumer.vscode-eslint"
],
"forwardPorts": [3000],
"portsAttributes": {
"3000": { "label": "App Server", "onAutoForward": "notify" }
},
"env": {
"APP_ENV": "development",
"NODE_OPTIONS": "--max-old-space-size=4096"
},
"postCreateCommand": "npm ci",
"remoteUser": "appuser"
}What each field does
- build.dockerfile – tells Codespaces to build the image from the local
Dockerfileinstead of pulling a pre‑built image. - extensions – array of VS Code Marketplace IDs; they are installed automatically when the Codespace starts.
- forwardPorts – makes the listed container ports reachable via a generated HTTPS URL while the Codespace runs.
- portsAttributes – optional UI hints (label, notification) for forwarded ports.
- env – environment variables injected into the Codespace shell and any process started from it.
- postCreateCommand – runs after the container is up; here it installs npm dependencies.
- remoteUser – matches the non‑root user created in the Dockerfile, avoiding permission issues.
How to verify the setup
- Create a new GitHub repository (or use an existing one) and add the two files under
.devcontainer/. - Open the repository on GitHub, press Code → Codespaces → Create codespace on main.
- Wait for the build to finish (first run may take a minute; subsequent runs reuse cached layers).
- In the Codespace terminal run:
curl --version # Expected: curl 7.x.x (shows the tool installed in the Dockerfile) - Open the Extensions sidebar (Ctrl+Shift+X) and confirm Python and ESLint appear as installed.
- Start a simple server (e.g.,
npx serve -l 3000) and check the Ports tab – port 3000 should be listed with a public URL that loads the server response.
Limits and common mistakes
Resource limits
Default Codespaces provide 1 CPU, 2 GB RAM, and 30 GB disk. A Dockerfile that installs heavy tooling (large compilers, multiple language runtimes) can exceed these limits, causing OOM kills or slow starts. Use a minimal base image (e.g., node:18-alpine or ubuntu:22.04) and consider multi‑stage builds to keep the final image small.
Extension compatibility
Not every VS Code extension works in the browser‑based Codespace. Extensions that ship native binaries compiled for Linux x86_64 usually work; those requiring Windows/macOS binaries or kernel modules will fail to activate. Test each extension after the first build.
Environment variable propagation
Variables in devcontainer.json are set in the Codespace shell only. They are not automatically passed to child containers started via docker run or docker-compose unless you explicitly forward them (e.g., docker run -e APP_ENV).
Port forwarding lifecycle
Forwarded ports exist only while the Codespace is running. Closing the browser tab or stopping the Codespace terminates the tunnels. Persistent services need a separate hosting solution.
Private registry authentication
If your Dockerfile pulls from a private registry, provide credentials via a .docker/config.json committed to the repo (not recommended for secrets) or, preferably, store a GitHub Actions secret named DOCKER_CONFIG and reference it in a Codespace‑creation workflow. Without authentication the build fails with a 401 error.
Forgetting to rebuild
Changes to Dockerfile or devcontainer.json require a rebuild. Use the Command Palette → Codespaces: Rebuild Container or delete the existing Codespace and create a new one. The UI will show a “Rebuild” button when the configuration has changed.
Practical checklist
| Check | How to confirm |
|---|---|
| Base image size | Run docker images in the Codespace terminal; final image should be < 500 MB for typical Node/Python stacks. |
| Layer caching | Second Codespace creation should finish in < 30 seconds; logs show “Using cache” for unchanged steps. |
| Extension activation | Open Extensions view; no “Install” button next to listed IDs. |
| Env vars present | echo $APP_ENV prints development. |
| Port reachable | Click the generated URL in the Ports tab; browser shows the running app. |
Following this pattern gives every contributor a reproducible, pre‑configured environment without manual setup steps.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.