Scaffold a production-parity Docker development environment: multi-service Compose, volume mounts, networking, health checks, dev containers, and secrets management. Eliminates "works on my machine" at the infrastructure layer.
From sanpx claudepluginhub javimontano/jm-adk --plugin sovereign-architectThis skill is limited to using the following tools:
agents/dockerfile-optimizer-agent.mdagents/scaffold-docker-dev-agent.mdevals/evals.jsonexamples/sample-output.mdprompts/use-case-prompts.mdreferences/body-of-knowledge.mdreferences/knowledge-graph.mmdreferences/state-of-the-art.md"Your dev environment is infrastructure — version-control it, test it, and treat drift as a bug."
Five-step procedure to build a Docker-based dev environment with multi-service Compose orchestration, bind mounts for live reload, network segmentation, health-check-gated startup ordering, and Dev Container integration for VS Code / Cursor.
.env files.compose.yml Structure# compose.yml — development override (not production)
name: myapp-dev
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
args:
NODE_VERSION: "20"
ports:
- "3000:3000"
- "9229:9229" # Node debugger
volumes:
- .:/workspace:cached # Bind mount — live code
- node_modules:/workspace/node_modules # Anonymous vol — prevents host override
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://cache:6379
env_file:
- .env.local
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
networks:
- backend
develop:
watch:
- action: sync
path: ./src
target: /workspace/src
- action: rebuild
path: package.json
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./infra/db/init:/docker-entrypoint-initdb.d:ro
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
networks:
- backend
cache:
image: redis:7-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 3
networks:
- backend
mailpit:
image: axllent/mailpit:latest
ports:
- "8025:8025" # Web UI
- "1025:1025" # SMTP
networks:
- backend
volumes:
postgres_data:
redis_data:
node_modules:
networks:
backend:
driver: bridge
# For apps requiring frontend/backend isolation
networks:
frontend:
driver: bridge
backend:
driver: bridge
# Services on both networks act as reverse proxy
# e.g. nginx on frontend+backend; db only on backend
# Dockerfile.dev — optimized for development iteration speed
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine AS base
# Install system deps (rarely changes — cache this layer)
RUN apk add --no-cache libc6-compat curl git
WORKDIR /workspace
# Install deps separately from app code (cache invalidation boundary)
COPY package*.json ./
RUN npm ci
# Dev: don't copy source — it's bind-mounted
COPY tsconfig.json ./
EXPOSE 3000 9229
# Use exec form — PID 1 receives signals correctly
CMD ["npm", "run", "dev"]
# Dockerfile — production multi-stage
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 appuser
COPY --from=builder --chown=appuser:nodejs /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
| Service | Test Command | Interval | Retries |
|---|---|---|---|
| PostgreSQL | pg_isready -U user -d db | 5s | 5 |
| MySQL | mysqladmin ping -h localhost | 5s | 5 |
| Redis | redis-cli ping | 5s | 3 |
| HTTP API | curl -f http://localhost:3000/health | 10s | 3 |
| RabbitMQ | rabbitmq-diagnostics ping | 10s | 5 |
// src/routes/health.ts
import { Router } from 'express';
import { db } from '../db';
import { cache } from '../cache';
export const healthRouter = Router();
healthRouter.get('/health', async (req, res) => {
const checks = await Promise.allSettled([
db.raw('SELECT 1'),
cache.ping(),
]);
const status = checks.every(c => c.status === 'fulfilled') ? 'ok' : 'degraded';
res.status(status === 'ok' ? 200 : 503).json({
status,
db: checks[0].status,
cache: checks[1].status,
uptime: process.uptime(),
});
});
// .devcontainer/devcontainer.json
{
"name": "MyApp Dev",
"dockerComposeFile": ["../compose.yml", "docker-compose.devcontainer.yml"],
"service": "app",
"workspaceFolder": "/workspace",
"shutdownAction": "stopCompose",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-azuretools.vscode-docker",
"ckolkman.vscode-postgres"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.tsdk": "node_modules/typescript/lib"
}
}
},
"postCreateCommand": "npm run db:migrate && npm run db:seed",
"forwardPorts": [3000, 5432, 8025],
"remoteUser": "node"
}
# .devcontainer/docker-compose.devcontainer.yml — overrides for devcontainer
services:
app:
command: sleep infinity # devcontainer manages the process
environment:
- SHELL=/bin/zsh
.dockerignore Discipline# .dockerignore — critical for build context size
node_modules
.git
.env*
!.env.example
dist
coverage
.next
*.log
depends_on — Services never start before dependencies are healthy.node_modules — Prevents host node_modules from masking container version.env_file or Docker secrets for sensitive values..dockerignore present — Build context excludes node_modules, .git, .env.CMD uses exec form ["node", "..."], not shell form, so PID 1 receives SIGTERM.USER appuser set before CMD.depends_on: without condition: service_healthy — Race conditions where app starts before DB is ready, causing startup crashes.node_modules from host — Different OS/arch between host (macOS) and container (Linux) causes native module incompatibility..dockerignore — Sending node_modules (potentially GBs) as build context on every build.CMD npm start) — Node process becomes a child of /bin/sh; SIGTERM not forwarded, causes 10s shutdown timeout.0.0.0.0 — Other network participants can reach your dev DB; bind to 127.0.0.1:5432:5432 instead.Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.