Docker Development Environment Setup: Complete Guide 2026
Step-by-step guide to creating a production-ready Docker development environment with hot reload, debugging, and Docker Compose.
Docker Development Environment Setup: Complete Guide 2026#
Setting up Docker for development can transform your workflow, but getting it right takes some know-how. After years of helping teams containerize their applications, I've learned what actually works in real-world development.
Related reading: Check out our guides on TypeScript migration and remote work setup for more development insights.
Why Docker for Development?#
The Real Benefits#
Consistency across environments:
- "Works on my machine" becomes "works everywhere"
- Same environment for all team members
- Identical dev, staging, and production setups
Faster onboarding:
- New developers productive in minutes, not days
- No complex installation instructions
- One command to start everything
Isolation and cleanup:
- Multiple projects without conflicts
- Easy to tear down and rebuild
- No leftover dependencies cluttering your system
Production parity:
- Catch environment-specific bugs early
- Test with production-like services
- Smooth deployment process
When Docker Makes Sense#
Perfect for:
- Full-stack applications with multiple services
- Projects with complex dependencies
- Teams with different operating systems
- Microservices architectures
Skip Docker if:
- Building simple static sites
- Working solo on small scripts
- Learning a new language (native install is simpler)
- Your team has zero Docker experience and tight deadlines
Prerequisites#
Before diving in, make sure you have:
Required:
- Docker Desktop installed (download here)
- Basic command line knowledge
- A code editor (VS Code recommended)
Helpful:
- Understanding of your application's architecture
- Familiarity with environment variables
- Basic networking concepts
System requirements:
- 8GB RAM minimum (16GB recommended)
- 20GB free disk space
- Windows 10/11 Pro, macOS 10.15+, or Linux
How to Set Up Docker for Development#
Step 1: Install Docker Desktop#
macOS:
## Using Homebrew
brew install --cask docker
## Or download from docker.com
## Start Docker Desktop from Applications
Windows:
## Download Docker Desktop from docker.com
## Enable WSL 2 backend during installation
## Restart your computer
Linux (Ubuntu/Debian):
## Install Docker Engine
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
## Add your user to docker group
sudo usermod -aG docker $USER
## Install Docker Compose
sudo apt-get install docker-compose-plugin
Verify installation:
docker --version
docker compose version
Step 2: Create Your First Dockerfile#
Let's start with a Node.js application:
## Dockerfile
FROM node:20-alpine
## Set working directory
WORKDIR /app
## Copy package files
COPY package*.json ./
## Install dependencies
RUN npm ci
## Copy application code
COPY . .
## Expose port
EXPOSE 3000
## Start application
CMD ["npm", "run", "dev"]
Key concepts:
FROM: Base image to build fromWORKDIR: Sets the working directory inside containerCOPY: Copies files from host to containerRUN: Executes commands during buildCMD: Command to run when container starts
Step 3: Add Docker Compose#
Create docker-compose.yml for multi-service setup:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:password@db:5432/myapp
depends_on:
- db
- redis
command: npm run dev
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
Start your environment:
docker compose up
Step 4: Enable Hot Reload#
For Node.js with nodemon:
## Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
## Install nodemon globally
RUN npm install -g nodemon
COPY . .
EXPOSE 3000
CMD ["nodemon", "src/index.js"]
Update docker-compose.yml:
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- /app/node_modules
environment:
- CHOKIDAR_USEPOLLING=true
For React/Vite:
services:
frontend:
build: ./frontend
ports:
- "5173:5173"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- VITE_API_URL=http://localhost:3000
command: npm run dev -- --host
Step 5: Configure Environment Variables#
Create .env file:
## .env
NODE_ENV=development
DATABASE_URL=postgresql://user:password@db:5432/myapp
REDIS_URL=redis://redis:6379
API_KEY=your-api-key-here
Update docker-compose.yml:
services:
app:
env_file:
- .env
environment:
- NODE_ENV=${NODE_ENV}
- DATABASE_URL=${DATABASE_URL}
Important: Add .env to .gitignore!
Step 6: Set Up Debugging#
VS Code launch configuration (.vscode/launch.json):
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Docker: Attach to Node",
"port": 9229,
"address": "localhost",
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app",
"protocol": "inspector"
}
]
}
Update Dockerfile for debugging:
CMD ["node", "--inspect=0.0.0.0:9229", "src/index.js"]
Update docker-compose.yml:
services:
app:
ports:
- "3000:3000"
- "9229:9229"
Step 7: Optimize Build Performance#
Use .dockerignore:
node_modules
npm-debug.log
.git
.env
.DS_Store
dist
build
coverage
Multi-stage builds for production:
## Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
## Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Layer caching:
## Copy package files first (changes less frequently)
COPY package*.json ./
RUN npm ci
## Copy source code last (changes frequently)
COPY . .
Common Development Patterns#
Full-Stack Application#
version: '3.8'
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- REACT_APP_API_URL=http://localhost:4000
backend:
build: ./backend
ports:
- "4000:4000"
volumes:
- ./backend:/app
- /app/node_modules
environment:
- DATABASE_URL=postgresql://user:password@db:5432/myapp
depends_on:
- db
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Microservices Setup#
version: '3.8'
services:
api-gateway:
build: ./services/gateway
ports:
- "8080:8080"
depends_on:
- auth-service
- user-service
auth-service:
build: ./services/auth
environment:
- JWT_SECRET=${JWT_SECRET}
- DATABASE_URL=${AUTH_DB_URL}
user-service:
build: ./services/users
environment:
- DATABASE_URL=${USER_DB_URL}
message-queue:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
Useful Docker Commands#
Daily Development#
## Start services
docker compose up
## Start in background
docker compose up -d
## Stop services
docker compose down
## Rebuild and start
docker compose up --build
## View logs
docker compose logs -f app
## Execute command in running container
docker compose exec app npm install package-name
## Open shell in container
docker compose exec app sh
Debugging and Maintenance#
## List running containers
docker ps
## List all containers
docker ps -a
## View container logs
docker logs container-name
## Inspect container
docker inspect container-name
## Remove stopped containers
docker container prune
## Remove unused images
docker image prune
## Remove everything (careful!)
docker system prune -a
Database Operations#
## Access PostgreSQL
docker compose exec db psql -U user -d myapp
## Run migrations
docker compose exec app npm run migrate
## Seed database
docker compose exec app npm run seed
## Backup database
docker compose exec db pg_dump -U user myapp > backup.sql
## Restore database
docker compose exec -T db psql -U user myapp < backup.sql
Troubleshooting Common Issues#
Port Already in Use#
Error: Bind for 0.0.0.0:3000 failed: port is already allocated
Solution:
## Find process using port
lsof -i :3000 # macOS/Linux
netstat -ano | findstr :3000 # Windows
## Kill the process or change port in docker-compose.yml
ports:
- "3001:3000"
Volume Permission Issues#
Error: EACCES: permission denied
Solution:
## Add user in Dockerfile
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
USER appuser
Slow Performance on macOS/Windows#
Problem: File sync is slow with volumes
Solution:
## Use delegated or cached mode
volumes:
- .:/app:delegated
- /app/node_modules
Or use Docker volumes instead:
volumes:
- app_data:/app
volumes:
app_data:
Container Keeps Restarting#
Check logs:
docker compose logs app
Common causes:
- Application crashes on startup
- Missing environment variables
- Database not ready (add healthcheck)
Solution with healthcheck:
services:
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 5s
retries: 5
app:
depends_on:
db:
condition: service_healthy
Best Practices#
Security#
Don't run as root:
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
USER appuser
Use specific image versions:
## Bad
FROM node:latest
## Good
FROM node:20.11-alpine
Scan for vulnerabilities:
docker scan your-image:tag
Performance#
Minimize layers:
## Bad
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
## Good
RUN apt-get update && \
apt-get install -y package1 package2 && \
rm -rf /var/lib/apt/lists/*
Use alpine images:
FROM node:20-alpine # ~40MB
## vs
FROM node:20 # ~900MB
Maintainability#
Document your setup:
## README.md
## Development Setup
1. Install Docker Desktop
2. Copy `.env.example` to `.env`
3. Run `docker compose up`
4. Access app at http://localhost:3000
## Common Commands
- `docker compose up` - Start services
- `docker compose down` - Stop services
- `docker compose logs -f` - View logs
Use make for common tasks:
## Makefile
.PHONY: up down logs shell test
up:
docker compose up -d
down:
docker compose down
logs:
docker compose logs -f
shell:
docker compose exec app sh
test:
docker compose exec app npm test
Frequently Asked Questions#
Q: Should I use Docker for development or just production? A: Use Docker for both. Development with Docker ensures your local environment matches production, catching environment-specific bugs early. The initial setup time pays off quickly with consistent environments across your team.
Q: How do I handle database migrations in Docker?
A: Run migrations as part of your startup script or as a separate service. Add a migration command to your docker-compose.yml: docker compose exec app npm run migrate. For production, run migrations before deploying new code.
Q: Why is my Docker container so slow on macOS?
A: File system performance with volumes can be slow on macOS. Use :delegated or :cached flags on volumes, or use named volumes instead of bind mounts for node_modules and other frequently accessed directories.
Q: How do I debug inside a Docker container? A: Expose the debug port (9229 for Node.js) in docker-compose.yml and use your IDE's remote debugging feature. VS Code has excellent Docker debugging support with the Docker extension.
Q: Should I commit my docker-compose.yml to git? A: Yes, commit docker-compose.yml but not .env files. Create a .env.example with dummy values for documentation. Each developer creates their own .env from the example.
Q: How do I update dependencies in a Docker container?
A: Run docker compose exec app npm install package-name to install in the running container, or rebuild with docker compose up --build after updating package.json locally.
Q: What's the difference between CMD and ENTRYPOINT? A: CMD provides default arguments that can be overridden. ENTRYPOINT defines the main command that always runs. Use CMD for development (easy to override) and ENTRYPOINT for production (consistent behavior).
Q: How do I share my Docker setup with the team? A: Commit Dockerfile, docker-compose.yml, and .dockerignore to git. Create a .env.example file. Document setup steps in README.md. Consider using a Makefile for common commands.
Conclusion#
Docker transforms development from "works on my machine" to "works everywhere." The key is starting simple and adding complexity only when needed.
Your action plan:
- Install Docker Desktop
- Create a basic Dockerfile
- Add docker-compose.yml for services
- Enable hot reload for your framework
- Document the setup for your team
The initial setup takes time, but you'll save hours every week with consistent environments and faster onboarding.
Further Reading:
- Docker Official Documentation - Comprehensive Docker guide
- Docker Compose Documentation - Multi-container applications
- Learn more about our editorial team and how we research our articles.
Related Articles#
Explore more articles in our Deployment & DevOps series:
- Deploying Next.js + Supabase to Production - Complete guide covering all aspects
- More related articles coming soon
Advanced Docker Networking#
Multi-Container Communication#
# docker-compose.yml with networking
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
networks:
- app-network
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
redis:
image: redis:7-alpine
networks:
- app-network
# Service discovery example
api:
build: ./api
environment:
- APP_HOST=app
- APP_PORT=3000
depends_on:
- app
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
postgres_data:
Custom Network Configuration#
# Advanced networking with custom DNS
version: '3.8'
services:
app:
build: .
networks:
app-network:
ipv4_address: 172.20.0.2
aliases:
- myapp.local
- api.local
db:
image: postgres:15
networks:
app-network:
ipv4_address: 172.20.0.3
aliases:
- database.local
networks:
app-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
Production-Ready Patterns#
Health Checks#
# docker-compose.yml with health checks
version: '3.8'
services:
app:
build: .
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
Resource Limits#
# docker-compose.yml with resource constraints
version: '3.8'
services:
app:
build: .
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
db:
image: postgres:15
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '1'
memory: 512M
Debugging in Docker#
Node.js Debugging#
# docker-compose.yml with debug port exposed
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
- "9229:9229" # Node.js debug port
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
command: node --inspect=0.0.0.0:9229 server.js
VS Code Debug Configuration#
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Docker Node Debug",
"type": "node",
"request": "attach",
"port": 9229,
"address": "localhost",
"restart": true,
"skipFiles": ["<node_internals>/**"]
}
]
}
Database Debugging#
# Connect to PostgreSQL in Docker
docker compose exec db psql -U user -d myapp
# View logs
docker compose logs -f db
# Execute SQL directly
docker compose exec db psql -U user -d myapp -c "SELECT * FROM users;"
Performance Optimization#
Layer Caching#
# Dockerfile optimized for layer caching
FROM node:18-alpine
WORKDIR /app
# Copy package files first (changes less frequently)
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy source code (changes frequently)
COPY . .
# Build
RUN npm run build
# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=0 /app/node_modules ./node_modules
COPY --from=0 /app/.next ./.next
COPY --from=0 /app/package*.json ./
EXPOSE 3000
CMD ["npm", "start"]
Volume Performance on macOS#
# docker-compose.yml with optimized volumes
version: '3.8'
services:
app:
build: .
volumes:
# Use delegated for one-way sync (app → host)
- .:/app:delegated
# Use cached for two-way sync with caching
- ./src:/app/src:cached
# Use named volume for node_modules (faster)
- node_modules:/app/node_modules
# Exclude directories
- /app/node_modules
- /app/.next
volumes:
node_modules:
Monitoring and Logging#
Centralized Logging#
# docker-compose.yml with logging driver
version: '3.8'
services:
app:
build: .
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "service=app"
db:
image: postgres:15
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "service=database"
View Logs#
# View all logs
docker compose logs
# Follow logs in real-time
docker compose logs -f
# View logs for specific service
docker compose logs -f app
# View last 100 lines
docker compose logs --tail=100
# View logs with timestamps
docker compose logs -t
Common Issues and Solutions#
Port Already in Use#
# Find process using port
lsof -i :3000
# Kill process
kill -9 <PID>
# Or use different port in docker-compose.yml
ports:
- "3001:3000"
Slow File System on macOS#
# Use named volumes instead of bind mounts
version: '3.8'
services:
app:
build: .
volumes:
# Instead of: - .:/app
# Use named volume:
- app_data:/app
- node_modules:/app/node_modules
volumes:
app_data:
node_modules:
Out of Disk Space#
# Clean up unused images
docker image prune
# Clean up unused containers
docker container prune
# Clean up everything
docker system prune -a
# Check disk usage
docker system df
Testing in Docker#
Run Tests in Container#
# docker-compose.yml with test service
version: '3.8'
services:
app:
build: .
environment:
- NODE_ENV=development
test:
build: .
command: npm test
environment:
- NODE_ENV=test
depends_on:
- db
volumes:
- .:/app
db:
image: postgres:15
environment:
- POSTGRES_DB=test
Run Tests#
# Run tests once
docker compose run test
# Run tests with coverage
docker compose run test npm run test:coverage
# Run specific test file
docker compose run test npm test -- auth.test.ts
Best Practices Checklist#
□ Use .dockerignore to exclude unnecessary files
□ Use multi-stage builds for smaller images
□ Pin base image versions (not latest)
□ Run as non-root user in production
□ Use environment variables for configuration
□ Set resource limits
□ Add health checks
□ Use named volumes for persistent data
□ Commit docker-compose.yml to git
□ Create .env.example for documentation
□ Use specific versions for dependencies
□ Optimize layer caching
□ Use Alpine images for smaller size
□ Implement proper logging
□ Test in production-like environment
Related Articles#
- Kubernetes & Docker Container Orchestration 2026
- Deploying Next.js + Supabase to Production
- Next.js Performance Optimization
- Building SaaS with Next.js and Supabase
- TypeScript JavaScript Migration Guide 2026
- Serverless & Edge Computing Revolution 2026
Conclusion#
Docker development environments transform how teams work together. The initial setup investment pays dividends through consistency, faster onboarding, and fewer "works on my machine" problems.
Start simple with a basic docker-compose.yml, then gradually add complexity as your needs grow. Use the patterns in this guide to build a development environment that scales with your team.
Remember: the best Docker setup is one your team actually uses. Keep it simple, document it well, and iterate based on feedback.
Frequently Asked Questions
Continue Reading
Kubernetes & Docker: Container Orchestration Mastery 2026
Learn Kubernetes and Docker from basics to production deployment. Includes real-world examples, scaling strategies, and DevOps best practices for 2026.
Next.js Performance Optimization: 10 Essential Techniques
Essential Next.js performance optimization techniques. Learn image optimization, caching, bundle splitting, and how to improve Core Web Vitals.
Progressive Web Apps (PWA): The Complete 2026 Guide
Learn how to build Progressive Web Apps that work offline, load instantly, and feel like native apps. Includes service workers, caching strategies, and push notifications.