sademban's blog

Blog Journal Portfolio About Now Uses Links 

Journal entries, build notes, and experiments from whatever I'm tinkering with next.

View on GitHub
14 October 2025

Local dev parity with devcontainers — fast feedback without the friction

by sademban

doodle image showing local dev-container parity
local and dev-container parity with fast feedback without friction

🟦 Local dev parity with devcontainers — fast feedback without the friction

TL;DR: Use a lightweight devcontainer that mirrors your production runtime for services, but keeps the feedback loop fast with layered Dockerfiles, cached volumes, and local compose helpers. This post shows a minimal devcontainer.json, a recommended Dockerfile, and a docker-compose snippet that keeps editing fast while matching production behavior.

Why this matters ?

Local development that behaves like production reduces surprising bugs, speeds onboarding, and makes CI results more predictable. Historically, achieving parity required heavy VM setups or brittle developer scripts. devcontainer (VS Code Remote - Containers) + docker-compose strikes a good balance: sandboxed, reproducible, and fast when configured correctly.

What we’ll cover ?

Minimal examples

  1. devcontainer.json

This is the entrypoint for VS Code. It installs the dev image, maps your sources, and runs optional container commands.

{
  "name": "MyApp Dev",
  "build": {
    "dockerfile": "Dockerfile",
    "context": ".."
  },
  "runArgs": [
    "--init",
    "--cap-add=SYS_PTRACE"
  ],
  "workspaceFolder": "/workspace",
  "settings": {
    "terminal.integrated.shell.linux": "/bin/bash"
  },
  "extensions": [
    "ms-vscode-remote.remote-containers",
    "ms-azuretools.vscode-docker"
  ],
  "forwardPorts": [3000, 9229],
  "mounts": [
    "source=${localWorkspaceFolder}/.cache,target=/workspace/.cache,type=bind,consistency=cached"
  ],
  "postCreateCommand": "./scripts/setup-dev.sh || true",
  "remoteUser": "vscode"
}
  1. Dockerfile (layered for fast rebuilds)

Use a two-stage/layered approach: dependencies first, app code later. This keeps the often-changing source files off the dependency-install layers.

FROM node:20-alpine AS base
WORKDIR /workspace

# Install build deps (cached unless package.json changes)
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci --silent

FROM deps AS dev
COPY . .
RUN addgroup -S vscode && adduser -S vscode -G vscode
USER vscode
ENV NODE_ENV=development
CMD ["npm", "run", "dev"]
  1. docker-compose.dev.yml

Run external services (db, redis) alongside the devcontainer. Use named volumes for persistent caches and bind-mount the source for live reload.

version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/workspace:cached
      - /workspace/node_modules
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
    depends_on:
      - db

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: myapp_dev
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

Practical tips for speed and parity

VS Code tasks and launch

Add tasks that call docker-compose -f docker-compose.dev.yml up --build app for a single-command start, and a docker-compose exec app task for running tests.

Example tasks.json snippet:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Dev: Up",
      "type": "shell",
      "command": "docker-compose -f docker-compose.dev.yml up --build",
      "isBackground": true,
      "problemMatcher": []
    }
  ]
}

Common pitfalls

When to use Dev Containers vs local install

Use devcontainers when:

Use local installs when:

Wrap-up and checklist

Further reading

tags: devcontainers - docker - developer-experience - productivity