Docker Development Environment Setup: Complete Guide 2026
technology

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.

Jan 27, 2026
10 min read
Docker Development Environment Setup: Complete Guide 2026

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 from
  • WORKDIR: Sets the working directory inside container
  • COPY: Copies files from host to container
  • RUN: Executes commands during build
  • CMD: 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:

  1. Install Docker Desktop
  2. Create a basic Dockerfile
  3. Add docker-compose.yml for services
  4. Enable hot reload for your framework
  5. 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:

Explore more articles in our Deployment & DevOps series:

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

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

|

Have more questions? Contact us