Skip to content

Docker for Development: Using Containers as Dev Environment

DodaTech Updated 2026-06-22 6 min read

In this tutorial, you'll learn Docker for development environments including Dockerfiles, volumes for hot-reloading, multi-stage builds, docker-compose for full-stack apps, and dev environment best practices.

Why Docker for Development Matters

"It works on my machine" is the classic developer frustration. Differences in operating systems, library versions, and configurations cause bugs that are hard to reproduce. Docker containers give every developer on your team the same environment -- the same OS, the same dependencies, the same everything. New team members can start contributing minutes after cloning the repo, not hours.

By the end of this guide, you will create Dockerized development environments with hot-reloading, multi-service setups, and production-ready builds.

What is Docker?

Docker is a platform for developing, shipping, and running applications in containers. Containers are lightweight, portable units that package application code with all its dependencies.

flowchart TD
  A[Dockerfile] --> B[Docker Build]
  B --> C[Docker Image]
  C --> D[Docker Container]
  E[Docker Compose] --> F[Service 1: Web]
  E --> G[Service 2: API]
  E --> H[Service 3: Database]
  D --> I[Can run anywhere]
  I --> J[Developer Machine]
  I --> K[CI Server]
  I --> L[Production Server]

Dockerfile for Development

A Dockerfile defines the environment. A development Dockerfile prioritizes the developer experience: it installs development tools, enables debugging, and mounts source code as a volume.

Node.js Development Dockerfile

FROM node:20-slim

WORKDIR /app

# Install development dependencies
RUN apt-get update && apt-get install -y \
  git \
  curl \
  && rm -rf /var/lib/apt/lists/*

# Copy package files first (layer caching)
COPY package*.json ./
RUN npm install

# Copy source code (mounted as volume in dev)
COPY . .

EXPOSE 3000

CMD ["npm", "run", "dev"]

Building and Running

# Build the image
docker build -t myapp-dev -f Dockerfile.dev .

# Run with volume for hot-reloading
docker run -v $(pwd):/app \
  -v /app/node_modules \
  -p 3000:3000 \
  myapp-dev

Expected Output

$ docker run -v $(pwd):/app -v /app/node_modules -p 3000:3000 myapp-dev

> myapp@1.0.0 dev
> vite

  VITE v6.0.0  ready in 345ms

  Local:   http://localhost:5173/
  Network: use --host to expose

Changes to source files outside the container are reflected inside because of the volume mount.

Python Development Dockerfile

FROM python:3.12-slim

WORKDIR /app

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

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

# Copy source code (mounted in dev)
COPY . .

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
docker build -t myapi-dev -f Dockerfile.dev .
docker run -v $(pwd):/app -p 8000:8000 myapi-dev

Docker Compose for Full-Stack Apps

Docker Compose lets you define and run multi-service applications.

# docker-compose.yml
version: '3.9'

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    depends_on:
      - api
    environment:
      - VITE_API_URL=http://localhost:8080

  api:
    build:
      context: ./api
      dockerfile: Dockerfile.dev
    ports:
      - "8080:8080"
    volumes:
      - ./api:/app
    depends_on:
      - db
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp

  db:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Starting the Stack

docker compose up -d

Expected Output

$ docker compose up -d
[+] Running 4/4
 ✔ Network myapp_default      Created
 ✔ Volume myapp_postgres_data  Created
 ✔ Container myapp-db-1       Started
 ✔ Container myapp-api-1      Started
 ✔ Container myapp-frontend-1 Started

Viewing Logs

docker compose logs -f

Tearing Down

docker compose down -v

Development Best Practices

Use Named Volumes for Database Data

services:
  db:
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Use Anonymous Volumes for node_modules

services:
  frontend:
    volumes:
      - ./frontend:/app
      - /app/node_modules  # Anonymous volume prevents overwriting

Environment-Specific Compose Files

# docker-compose.override.yml (auto-loaded in development)
services:
  api:
    environment:
      - DEBUG=true
      - LOG_LEVEL=debug
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up

Health Checks

services:
  api:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Multi-Stage Builds for Production

# Stage 1: Build
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Expected Size Comparison

$ docker images
REPOSITORY   TAG           IMAGE ID       SIZE
myapp-dev    latest        a1b2c3d4e5f6   1.2GB    # With dev tools + source
myapp-prod   latest        f6e5d4c3b2a1   25MB     # Only nginx + static files

Debugging Inside Containers

Attach to a Running Container

# Open a shell inside the container
docker exec -it myapp-api-1 /bin/bash

# Run a specific command
docker exec myapp-api-1 python manage.py migrate

Debug with VS Code

Create .devcontainer/devcontainer.json:

{
  "name": "My App Development",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "api",
  "workspaceFolder": "/app",
  "extensions": [
    "ms-python.python",
    "ms-azuretools.vscode-docker]
  ]
}

Common Errors

Problem Cause Fix
Permission denied writing to volume UID mismatch inside/outside container Use user: "${UID}:${GID}" in compose or fix permissions
npm install fails inside container Architecture mismatch Use platform-specific base images or --platform=linux/amd64
Hot reload not working File changes not propagated Ensure volume mount is correct, check poll: true in dev server config
port already allocated error Port conflict Change host port mapping (e.g., 3001:3000)
Container exits immediately Process exits Check CMD/entrypoint; run with -it to keep open

Practice Questions

1. What does the -v $(pwd):/app flag do in docker run?

It mounts the current directory as /app inside the container, enabling hot-reloading.

2. What is the purpose of Docker Compose?

To define and run multi-container Docker applications with a single YAML configuration file.

3. How do multi-stage builds reduce image size?

The final stage copies only the build artifacts (compiled code, static files) without build tools or source code.

4. What does <a href="/devops/docker-compose/">Docker Compose</a> logs -f do?

Tails the logs from all services in real time.

5. Why use - /app/node_modules as an anonymous volume?

It prevents the host's node_modules from overriding the container's, while still mounting the source code.

Challenge

Create a complete Docker Compose setup for a full-stack application with: a React frontend with Vite hot-reloading, a Python FastAPI backend with auto-reload, a PostgreSQL database with persistent volume, and an nginx reverse proxy for production. Include health checks and environment-specific configurations.

Real-World Task

Dockerize an existing development project. Create a development Dockerfile with hot-reloading, a Docker Compose file for any service dependencies (database, cache, message queue), and a production Dockerfile with multi-stage builds. Verify that a new team member can clone the repo and start contributing with <a href="/devops/docker-compose/">Docker Compose</a> up.

Should I use Docker for local development?

Yes. Docker ensures consistent environments across your team and eliminates "works on my machine" problems. The overhead is minimal with volume mounts for hot-reloading.

How do I handle database data in development?

Use named volumes in Docker Compose. The data persists across container restarts. Run <a href="/devops/docker-compose/">Docker Compose</a> down -v to delete volumes when you want a fresh database.

Can I use Docker on macOS?

Yes. Docker Desktop for Mac provides a complete Docker experience. It runs containers inside a lightweight VM.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro