Developer experience (DX) determines how fast new engineers become productive and how much friction the existing team encounters daily. Claude Code dramatically improves DX tasks: generating Docker Compose setups that work first-time, writing onboarding scripts that handle OS differences, and creating CLAUDE.md files that give every team member the same AI context.
This guide covers developer experience improvements with Claude Code: local setup automation, dev containers, onboarding scripts, and team tooling.
One-Command Local Setup
Create a docker-compose.yml for local development.
Services: PostgreSQL, Redis, the Node API, and a React frontend.
It should work on both Mac and Linux without modification.
# docker-compose.yml
name: myapp
services:
postgres:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: localpassword
POSTGRES_DB: appdb
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/db/init:/docker-entrypoint-initdb.d # Run SQL on first startup
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --save 60 1 # Persist to disk every 60s
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
api:
build:
context: ./api
dockerfile: Dockerfile.dev
ports:
- "8080:8080"
volumes:
- ./api:/app
- /app/node_modules # Don't overwrite node_modules from host
environment:
NODE_ENV: development
DATABASE_URL: postgresql://appuser:localpassword@postgres:5432/appdb
REDIS_URL: redis://redis:6379
JWT_SECRET: local-dev-secret-change-in-production
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
command: npm run dev # Should use --watch / nodemon
web:
build:
context: ./web
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./web:/app
- /app/node_modules
environment:
VITE_API_URL: http://localhost:8080
depends_on:
- api
# Optional: DB UI for local debugging
pgadmin:
image: dpage/pgadmin4:latest
profiles: ["tools"] # Only starts with: docker compose --profile tools up
ports:
- "5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
depends_on:
- postgres
volumes:
postgres_data:
redis_data:
# scripts/setup.sh — one-command setup
#!/bin/bash
set -e
echo "Setting up development environment..."
# Check dependencies
command -v docker >/dev/null 2>&1 || { echo "Docker required. Install: https://docs.docker.com/get-docker/"; exit 1; }
command -v node >/dev/null 2>&1 || { echo "Node.js 20+ required"; exit 1; }
# Copy .env if it doesn't exist
[ -f .env ] || cp .env.example .env
# Install dependencies
npm install --prefix api
npm install --prefix web
# Start services
docker compose up -d postgres redis
# Wait for postgres
echo "Waiting for PostgreSQL..."
until docker compose exec postgres pg_isready -U appuser -q; do
sleep 1
done
# Run migrations
npm run --prefix api migrate
# Seed dev data
npm run --prefix api db:seed
echo "✓ Setup complete. Run: docker compose up"
Dev Containers
Create a .devcontainer setup so any team member gets the
same environment in VS Code or GitHub Codespaces.
// .devcontainer/devcontainer.json
{
"name": "MyApp Dev",
"dockerComposeFile": ["../docker-compose.yml", "docker-compose.devcontainer.yml"],
"service": "devcontainer",
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "20" },
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"Prisma.prisma",
"bradlc.vscode-tailwindcss",
"ms-azuretools.vscode-docker",
"humao.rest-client",
"mtxr.sqltools",
"mtxr.sqltools-driver-pg"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
}
},
"forwardPorts": [3000, 8080, 5432],
"portsAttributes": {
"3000": { "label": "Frontend" },
"8080": { "label": "API" },
"5432": { "label": "PostgreSQL" }
},
"postCreateCommand": "bash .devcontainer/setup.sh",
"remoteUser": "node"
}
# .devcontainer/docker-compose.devcontainer.yml
services:
devcontainer:
image: mcr.microsoft.com/devcontainers/base:ubuntu
volumes:
- ../:/workspace:cached
command: sleep infinity
depends_on:
- postgres
- redis
networks:
- default
Pre-Commit Hooks
Set up pre-commit hooks that:
1. Run prettier formatting
2. Run ESLint (only on staged files)
3. Run type checking
4. Prevent commits with console.log in API code
// package.json (root)
{
"scripts": {
"prepare": "husky"
},
"lint-staged": {
"*.{ts,tsx}": [
"prettier --write",
"eslint --fix --max-warnings=0"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Run lint-staged on staged files only (fast)
npx lint-staged
# Type check (only if TS files changed)
CHANGED_TS=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(ts|tsx)$' | head -1)
if [ -n "$CHANGED_TS" ]; then
echo "Type checking..."
npm run type-check 2>&1 | tail -20
if [ $? -ne 0 ]; then
echo "❌ Type check failed. Run: npm run type-check"
exit 1
fi
fi
# Block console.log in API code
API_LOGS=$(git diff --cached -- 'api/src/**/*.ts' | grep '^+' | grep 'console\.log')
if [ -n "$API_LOGS" ]; then
echo "❌ Found console.log in API code (use logger instead):"
echo "$API_LOGS"
exit 1
fi
Team CLAUDE.md Template
Create a CLAUDE.md that every engineer on the team uses,
covering architecture decisions, coding conventions, and local workflow.
# MyApp
## Architecture
- **API**: Node.js 20, Express 4, TypeScript strict mode
- **Database**: PostgreSQL 16, Drizzle ORM for type-safe queries
- **Cache**: Redis 7 (sessions + hot data)
- **Frontend**: React 18, Vite, TanStack Query for server state
- **Auth**: JWT access tokens (15min) + refresh tokens (7d, httpOnly cookie)
## Local Development
```sh
# First time:
./scripts/setup.sh
# Daily:
docker compose up # Starts postgres + redis
npm run dev --prefix api # API on :8080 with hot reload
npm run dev --prefix web # Frontend on :3000 with HMR
Key Decisions (ask before changing)
- No ORMs for complex queries — use Drizzle’s sql“ tag or raw SQL
- Service objects for business logic (src/services/) — not in controllers
- No try/catch in controllers — use express-async-errors + global error handler
- No
anytypes — if you need escape hatch, useunknown+ type assertion
Testing
npm test # Unit tests (vitest)
npm run test:e2e # E2E with Playwright (requires running app)
npm run test:ci # Both, no watch mode
PR Process
- Branch from
main:git checkout -b feat/your-feature - Run tests before push:
npm test - PRs require 1 approval; don’t merge your own
- Squash merge to main — no individual commits on main
Common Commands
npm run migrate # Run pending migrations
npm run migrate:create # Create new migration file
npm run db:reset # Drop + recreate (dev only)
npm run typecheck # TypeScript without emitting
npm run lint # ESLint check
npm run format # Prettier fix
Environment Variables
See .env.example — all required vars documented there.
Never commit .env files. Use 1Password for sharing secrets locally.
For the CLAUDE.md patterns that make AI coding more effective for your whole team, see the [CLAUDE.md guide](/blog/claude-md-guide/). For automating repetitive development tasks with Claude Code hooks, see the [hooks guide](/blog/claude-code-hooks-guide/). The [Claude Skills 360 bundle](https://claudeskills360.com) includes developer tooling skill sets for onboarding automation, local setup, and team process improvement. Start with the [free tier](/free/) to try developer experience code generation.