Docker for Development: A Modern Workflow Guide
"Works on my machine" should never be an acceptable answer. Docker solves this by packaging your application with all its dependencies, ensuring it runs identically everywhere—from your laptop to production.
Why Docker for Development?
Consistency
Everyone on your team runs the same environment. No more "I have version X, you have version Y" problems.
Isolation
Each project gets its own environment. No conflicts between projects requiring different Node versions or database versions.
Easy Onboarding
New team members can get started in minutes, not days.
Production Parity
Your development environment closely matches production, catching environment-specific issues early.
Getting Started
Basic Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Docker Compose for Multi-Container Apps
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:15-alpine
environment:
- POSTGRES_PASSWORD=pass
- POSTGRES_USER=user
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
Development Best Practices
Use Multi-Stage Builds
Separate your development and production images:
# Development stage
FROM node:20-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
# Production stage
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["npm", "start"]
Build for development:
docker build --target development -t myapp:dev .
Mount Source Code as Volume
Enable hot reloading by mounting your code:
services:
app:
volumes:
- .:/app # Mount source code
- /app/node_modules # But don't overwrite node_modules
Use .dockerignore
Exclude unnecessary files:
node_modules
npm-debug.log
.env
.git
.gitignore
README.md
.vscode
.idea
dist
build
Environment Variables
Use .env files for local development:
services:
app:
env_file:
- .env.local
Never commit .env files with secrets!
Database Persistence
Always use volumes for databases:
services:
postgres:
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Useful Commands
# Start all services
docker compose up
# Start in detached mode
docker compose up -d
# Rebuild containers
docker compose up --build
# Stop all services
docker compose down
# Stop and remove volumes (fresh start)
docker compose down -v
# View logs
docker compose logs -f app
# Execute commands in running container
docker compose exec app npm test
# Open shell in container
docker compose exec app sh
# List running containers
docker compose ps
Development Workflow
Initial Setup
# Clone repository
git clone https://github.com/yourteam/project.git
cd project
# Start containers
docker compose up -d
# Run migrations
docker compose exec app npm run migrate
# Open app
open http://localhost:3000
Daily Development
# Start your day
docker compose up -d
# Run tests
docker compose exec app npm test
# Run database migrations
docker compose exec app npm run migrate
# End your day
docker compose down
Debugging
# View logs
docker compose logs -f app
# Check container status
docker compose ps
# Restart a service
docker compose restart app
# Rebuild after dependency changes
docker compose up --build
Common Issues and Solutions
Port Already in Use
# Find what's using the port
lsof -i :3000
# Use a different port in docker-compose.yml
ports:
- "3001:3000"
Permission Issues (Linux)
# Match container user to host user
ARG USER_ID=1000
ARG GROUP_ID=1000
RUN addgroup -g ${GROUP_ID} user && \
adduser -D -u ${USER_ID} -G user user
USER user
Slow Performance on Mac
Use delegated or cached volumes:
volumes:
- .:/app:cached
- /app/node_modules
Database Not Ready
Add health checks:
services:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 5s
retries: 5
app:
depends_on:
db:
condition: service_healthy
Performance Optimization
Layer Caching
Order Dockerfile commands from least to most frequently changed:
# These rarely change - cached
FROM node:20-alpine
WORKDIR /app
# These change occasionally - cached if package.json unchanged
COPY package*.json ./
RUN npm ci
# These change frequently - rebuilt often
COPY . .
CMD ["npm", "run", "dev"]
BuildKit
Enable BuildKit for faster builds:
export DOCKER_BUILDKIT=1
docker build .
Or in docker-compose.yml:
services:
app:
build:
context: .
dockerfile: Dockerfile
cache_from:
- myapp:latest
Beyond Development
Once you're comfortable with Docker for development, you're 90% of the way to using it in production. The same Dockerfile (with different stages) can be used for:
- CI/CD pipelines
- Staging environments
- Production deployment
Getting Started Today
- Install Docker Desktop
- Add a
Dockerfileanddocker-compose.ymlto your project - Run
docker compose up - That's it!
Need help containerizing your application? Let's chat.