Docker Compose — Full Dev Environment Project
Build a complete local development environment with a web app, database, cache, mail catcher, and monitoring — combining every Compose concept from this tutorial.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ Docker Network: dev-net │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
│ │ Web App │ │ Postgres │ │ Redis │ │ MailHog │ │
│ │ :3000 │ │ :5432 │ │ :6379 │ │ :1025/:8025 │ │
│ └────┬─────┘ └──────────┘ └──────────┘ └────────────────┘ │
│ │ │
│ Compose Watch ┌────────────────────────┐ │
│ (hot reload) [debug profile] │ Adminer │ pgAdmin │ │
│ │ :8080 │ :5050 │ │
│ └────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Project Setup
mkdir fulldev-compose && cd fulldev-compose
mkdir app
Step 1 — Application Code
Create app/package.json:
{
"name": "fulldev-app",
"version": "1.0.0",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
},
"dependencies": {
"express": "^4.18.0",
"pg": "^8.11.0",
"redis": "^4.6.0",
"nodemailer": "^6.9.0"
}
}
Create app/server.js:
const express = require('express');
const { Pool } = require('pg');
const { createClient } = require('redis');
const nodemailer = require('nodemailer');
const app = express();
app.use(express.json());
const pool = new Pool({
host: 'postgres', port: 5432,
database: 'devdb', user: 'devuser', password: 'devpass'
});
const redis = createClient({ url: 'redis://redis:6379' });
redis.connect().catch(console.error);
const mailer = nodemailer.createTransport({
host: 'mailhog', port: 1025
});
app.get('/health', async (req, res) => {
res.json({ status: 'ok', time: new Date().toISOString() });
});
app.post('/notify', async (req, res) => {
await mailer.sendMail({
from: 'app@example.com', to: 'user@example.com',
subject: 'Test Email', text: 'Sent from Compose dev environment!'
});
res.json({ sent: true });
});
app.listen(3000, () => console.log('Dev server on :3000'));
Create app/Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Step 2 — Base Compose File
Create compose.yml:
services:
app:
build:
context: ./app
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
NODE_ENV: development
networks:
- dev-net
develop:
watch:
- action: sync
path: ./app
target: /app
ignore:
- node_modules/
- action: rebuild
path: ./app/package.json
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: devdb
POSTGRES_USER: devuser
POSTGRES_PASSWORD: devpass
volumes:
- pg-data:/var/lib/postgresql/data
networks:
- dev-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U devuser -d devdb"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
networks:
- dev-net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025"
- "8025:8025"
networks:
- dev-net
# Debug tools — only start with: docker compose --profile debug up
adminer:
image: adminer:latest
profiles:
- debug
ports:
- "8080:8080"
networks:
- dev-net
pgadmin:
image: dpage/pgadmin4:latest
profiles:
- debug
ports:
- "5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
networks:
- dev-net
volumes:
pg-data:
redis-data:
networks:
dev-net:
driver: bridge
Step 3 — Production Override
Create compose.prod.yml:
services:
app:
build:
context: ./app
command: ["npm", "start"]
environment:
NODE_ENV: production
deploy:
resources:
limits:
cpus: "0.5"
memory: 256m
postgres:
deploy:
resources:
limits:
memory: 512m
redis:
deploy:
resources:
limits:
memory: 128m
mailhog:
profiles:
- never
Step 4 — Launch for Development
Start the core services:
docker compose up -d --build
Start with debug tools:
docker compose --profile debug up -d
Use Compose Watch for hot reload:
docker compose watch
Step 5 — Test Everything
Health check:
curl http://localhost:3000/health
{"status":"ok","time":"2024-01-15T12:00:00.000Z"}
Send a test email:
curl -X POST http://localhost:3000/notify
{"sent":true}
View the email in MailHog at http://localhost:8025.
Step 6 — Simulate Production
docker compose -f compose.yml -f compose.prod.yml up -d --build
This merges the production overrides — MailHog is excluded (never profile), resources are limited, and the app runs in production mode.
Enhancements to Try
- Add Nginx as a reverse proxy in front of the app
- Add a frontend service with React/Vue
- Add database migrations as an init container
- Add Grafana + Prometheus behind a
monitoringprofile
Quick Reference
| Item | Detail |
|---|---|
| App port | 3000:3000 |
| MailHog SMTP | 1025:1025 |
| MailHog UI | 8025:8025 |
| Adminer | 8080:8080 (debug profile) |
| pgAdmin | 5050:80 (debug profile) |
| Compose Watch | docker compose watch |
| Prod override | compose.prod.yml |