A development container should include the tools a project needs without granting the container more access than necessary. This setup starts with a slim Node.js image, installs a focused toolchain, and runs the development environment as the non-root node user.
devcontainer.json
Create devcontainer.json in the project root inside a .devcontainer folder:
{
"name": "Hardened Dev Container",
"image": "hardened-base-image",
/*
"build": {
"dockerfile": "../../shared-devcontainer/Dockerfile",
"context": "."
},
*/
"remoteUser": "node",
"containerUser": "node",
"privileged": false,
"runArgs": ["--cap-drop=ALL"],
"securityOpt": ["no-new-privileges:true"],
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/${localWorkspaceFolderBasename},type=bind,consistency=cached",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"vscode-icons-team.vscode-icons",
],
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"workbench.iconTheme": "vscode-icons",
},
},
},
"forwardPorts": [3000, 5173, 8080],
"postCreateCommand": "echo 'Dev container ready'",
"mounts": [
"source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,readonly",
],
}
The configuration assumes a local Docker image named hardened-base-image. It runs as node, drops all Linux capabilities, and enables Docker’s no-new-privileges security option.
Dockerfile
Create a Dockerfile alongside the Dev Container configuration:
FROM node:24-bookworm-slim
ARG USERNAME=node
ENV DEBIAN_FRONTEND=noninteractive
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
bash \
jq \
unzip \
zip \
build-essential \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /workspaces \
&& chown -R ${USERNAME}:${USERNAME} /workspaces
RUN corepack enable \
&& corepack prepare pnpm@latest --activate
RUN npm install -g playwright@latest
RUN npx playwright install --with-deps chromium
RUN npm i -g @openai/codex
USER ${USERNAME}
WORKDIR /workspaces
The image includes Node.js 24, pnpm, Chromium through Playwright, and the Codex CLI. Package installation happens as root, then the final image switches back to the unprivileged node user.
Build and use the image
Build the image from the directory containing the Dockerfile:
docker build -t hardened-base-image .
Then open the project in VS Code and run Dev Containers: Reopen in Container. The workspace is mounted under /workspaces, and ports 3000, 5173, and 8080 are available for common local development servers.
For repeatable builds, replace the latest package versions with explicit versions and update them deliberately.