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.