Docker for Local Development
Docker lets you run applications in isolated containers on your local machine. Each container is a self-contained environment with its own runtime, dependencies, and configuration — completely isolated from your host system and from other containers. The core problem it solves: different projects often need different versions of PHP, Node.js, Python, or databases. Without Docker, you're juggling global installations and hoping nothing conflicts. With Docker, each project declares its own environment and it always works, regardless of what else is installed.
A container maps a port inside the container to a port on your localhost. A web app running on port 80 inside a container might be mapped to localhost:8080 on your machine. You hit the local port and the traffic routes into the container.
Core Concepts
Before commands make sense, three concepts matter:
- Image — a snapshot of an environment (e.g., "nginx:alpine" or "node:20"). Images are pulled from Docker Hub and used as templates.
- Container — a running instance of an image. You can run ten containers from the same image simultaneously.
- Volume — a persistent storage mount. Without a volume, data inside a container disappears when the container stops. Volumes keep data (like a database) alive across restarts.
Essential Commands
# Pull and run an nginx container, mapping port 8080 locally to port 80 in container
docker run -d -p 8080:80 --name my-nginx nginx
# Run a Node.js app, mounting current directory into container
docker run -d -p 3000:3000 -v $(pwd):/app node:20
# List running containers
docker ps
# Stop a container
docker stop my-nginx
# Remove a container
docker rm my-nginx
# View container logs (live)
docker logs -f my-nginx
# Run a command inside running container
docker exec -it my-nginx bash
# Pull an image without running it
docker pull postgres:16
Docker Compose — Multi-Container Apps
Most real applications need more than one service: a web server, a database, maybe a cache. Docker Compose defines all of them in a single compose.yml file and starts them together with docker compose up.
# compose.yml — PHP app with MySQL and phpMyAdmin
services:
app:
image: php:8.3-apache
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
volumes:
- db_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin
ports:
- "8081:80"
environment:
PMA_HOST: db
volumes:
db_data:
# Start all services
docker compose up -d
# Stop all services
docker compose down
# Stop and delete volumes (wipes database)
docker compose down -v
# View logs for all services
docker compose logs -f
Common Development Stacks
| Stack | Services | Default Ports |
|---|---|---|
| LEMP (Linux, Nginx, MySQL, PHP) | nginx, php-fpm, mysql | 8080 (web), 3306 (db) |
| Node + MongoDB | node, mongo | 3000 (app), 27017 (db) |
| Django + PostgreSQL | python, postgres | 8000 (app), 5432 (db) |
| Laravel + Redis + MySQL | php, redis, mysql | 8080, 6379, 3306 |
| WordPress | wordpress, mysql | 8080 (wp), 3306 (db) |
WordPress in Docker (2 minutes)
services:
wordpress:
image: wordpress
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_PASSWORD: secret
volumes:
- wp_data:/var/www/html
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: wordpress
volumes:
- db_data:/var/lib/mysql
volumes:
wp_data:
db_data:
Save as compose.yml, run docker compose up -d, then open localhost:8080.
Volume Mounts for Live Editing
The key to development workflows is mounting your local code into the container so edits appear immediately without rebuilding the image:
# Mount ./src into the container's web root
docker run -d \
-p 8080:80 \
-v $(pwd)/src:/var/www/html \
php:8.3-apache
Now edit files in ./src on your machine — the container sees the changes instantly.
Persistent Databases
Always use a named volume for database containers. Without it, every docker stop wipes your data:
# Named volume — data survives container restarts and removals
docker run -d \
-p 5432:5432 \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16
Troubleshooting
| Problem | Fix |
|---|---|
| Port already in use | Another service is on that port — change the host port: -p 8081:80 instead of 8080:80 |
| Container exits immediately | Run docker logs [container-name] to see the error |
| Changes not showing up | Confirm the volume mount path is correct — a typo silently fails |
| Database connection refused | In Compose, use the service name (db, not localhost) as the database host |
| Out of disk space | docker system prune -a removes unused images, containers, and volumes |
Related Tools
Docker pairs well with Portainer for a visual container management UI. The MySQL port 3306, PostgreSQL port 5432, and Redis port 6379 pages cover connecting to containerized databases from your host machine or another tool.