Docker for Developers: Complete Development Workflow Guide

Docker has revolutionized how we develop and deploy applications. Let’s explore how to leverage Docker for a seamless development workflow that works consistently across all environments.

Docker Fundamentals for Development

Essential Dockerfile Patterns

# Multi-stage build for Node.js
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:18-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]

FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

Optimized Python Dockerfile

FROM python:3.11-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

# Install system dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Create non-root user
RUN adduser --disabled-password --gecos '' appuser && \
    chown -R appuser:appuser /app
USER appuser

EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Docker Compose for Multi-Service Development

Full-Stack Application Setup

# docker-compose.yml
version: '3.8'

services:
  # Frontend React App
  frontend:
    build:
      context: ./frontend
      target: development
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - REACT_APP_API_URL=http://localhost:8000
    depends_on:
      - backend

  # Backend API
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    ports:
      - "8000:8000"
    volumes:
      - ./backend:/app
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis

  # PostgreSQL Database
  db:
    image: postgres:15-alpine
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql

  # Redis Cache
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  # Nginx Reverse Proxy
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - frontend
      - backend

volumes:
  postgres_data:
  redis_data:

Development Override File

# docker-compose.override.yml
version: '3.8'

services:
  backend:
    build:
      target: development
    volumes:
      - ./backend:/app
      - /app/__pycache__
    environment:
      - DEBUG=1
      - LOG_LEVEL=debug
    command: ["python", "manage.py", "runserver", "0.0.0.0:8000"]

  frontend:
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - FAST_REFRESH=true
      - CHOKIDAR_USEPOLLING=true

Development Environment Optimization

Hot Reloading Configuration

# Dockerfile.dev for Node.js with hot reload
FROM node:18-alpine

WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm install

# Install nodemon globally for hot reloading
RUN npm install -g nodemon

# Copy source code
COPY . .

EXPOSE 3000

# Use nodemon for development
CMD ["nodemon", "--legacy-watch", "server.js"]

Volume Mounting Strategies

services:
  app:
    build: .
    volumes:
      # Bind mount source code for hot reloading
      - ./src:/app/src
      
      # Anonymous volume for node_modules to avoid conflicts
      - /app/node_modules
      
      # Named volume for persistent data
      - app_data:/app/data
      
      # Bind mount config files
      - ./config:/app/config:ro  # Read-only

Database Development Patterns

Database Initialization

-- db/init.sql
CREATE DATABASE IF NOT EXISTS myapp_dev;
CREATE DATABASE IF NOT EXISTS myapp_test;

CREATE USER IF NOT EXISTS 'dev_user'@'%' IDENTIFIED BY 'dev_password';
GRANT ALL PRIVILEGES ON myapp_dev.* TO 'dev_user'@'%';
GRANT ALL PRIVILEGES ON myapp_test.* TO 'dev_user'@'%';

-- Create initial tables
USE myapp_dev;

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Database Seeding

services:
  db-seed:
    build:
      context: ./database
      dockerfile: Dockerfile.seed
    depends_on:
      - db
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
    volumes:
      - ./database/seeds:/seeds
    command: ["python", "seed.py"]

Testing with Docker

Test Environment Setup

# docker-compose.test.yml
version: '3.8'

services:
  test-db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=test_db
      - POSTGRES_USER=test_user
      - POSTGRES_PASSWORD=test_password
    tmpfs:
      - /var/lib/postgresql/data  # In-memory for faster tests

  app-test:
    build:
      context: .
      target: test
    environment:
      - NODE_ENV=test
      - DATABASE_URL=postgresql://test_user:test_password@test-db:5432/test_db
    depends_on:
      - test-db
    command: ["npm", "test"]

Running Tests

# Run tests in isolated environment
docker-compose -f docker-compose.test.yml up --build --abort-on-container-exit

# Run specific test suite
docker-compose -f docker-compose.test.yml run --rm app-test npm run test:unit

# Interactive testing
docker-compose -f docker-compose.test.yml run --rm app-test bash

Production-Ready Configurations

Health Checks

# Add health check to Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1
# Health checks in docker-compose
services:
  app:
    build: .
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Resource Limits

services:
  app:
    build: .
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
    restart: unless-stopped

Useful Docker Commands

Development Workflow Commands

# Build and start all services
docker-compose up --build

# Start services in background
docker-compose up -d

# View logs
docker-compose logs -f app

# Execute commands in running container
docker-compose exec app bash
docker-compose exec db psql -U postgres

# Clean up
docker-compose down -v  # Remove volumes
docker system prune -a  # Clean up everything

# Database operations
docker-compose exec db pg_dump -U postgres myapp > backup.sql
docker-compose exec -T db psql -U postgres myapp < backup.sql

Debugging Commands

# Inspect container
docker inspect container_name

# Check resource usage
docker stats

# View container processes
docker-compose top

# Debug networking
docker network ls
docker network inspect network_name

Docker Development Best Practices

.dockerignore Optimization

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output
.vscode
.idea
*.log
dist
build

Environment-Specific Configurations

# .env.development
NODE_ENV=development
DEBUG=1
DATABASE_URL=postgresql://postgres:password@localhost:5432/myapp_dev
REDIS_URL=redis://localhost:6379

# .env.production
NODE_ENV=production
DATABASE_URL=${DATABASE_URL}
REDIS_URL=${REDIS_URL}

Docker transforms development from “works on my machine” to “works everywhere.” Master these patterns, and you’ll have consistent, reproducible development environments that scale from local development to production deployment.