Docker for Development: Using Containers as Dev Environment
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.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro