Docker Compose Examples for Node.js Apps (2025)
Real Docker Compose examples for Node.js applications. Covers Node + PostgreSQL, Node + Redis, multi-service setups, hot reload, production configs, and common troubleshooting.
Docker Compose transforms local development for Node.js applications. Instead of installing and managing PostgreSQL, Redis, and other services locally, you define everything in a docker-compose.yml and run docker compose up. Everyone on the team gets an identical environment.
This guide covers practical Docker Compose setups for Node.js β from a minimal single-container dev setup to production-ready multi-service configurations.
Prerequisites
Make sure you have Docker and Docker Compose installed:
# Check versions
docker --version # Docker 24.x+
docker compose version # Docker Compose 2.x+
Docker Desktop (macOS/Windows) includes both. On Linux, install Docker Engine and the Compose plugin separately.
1. Minimal Node.js Setup
Start simple: a single containerized Node.js app.
Project structure:
my-app/
βββ src/
β βββ index.js
βββ package.json
βββ Dockerfile
βββ docker-compose.yml
Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]
docker-compose.yml:
services:
app:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: production
Run it:
docker compose up --build
This builds the image and starts the container. Visit http://localhost:3000.
2. Node.js + PostgreSQL
The most common full-stack setup.
docker-compose.yml:
services:
app:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: development
DATABASE_URL: postgresql://postgres:password@db:5432/myapp
depends_on:
db:
condition: service_healthy
volumes:
- ./src:/app/src # hot reload
command: npm run dev
db:
image: postgres:16-alpine
restart: unless-stopped
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 # seed data
ports:
- "5432:5432" # expose for local DB clients (TablePlus, pgAdmin)
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:
Key points:
depends_onwithservice_healthywaits for Postgres to be ready before starting the app β eliminates connection race conditions- The
healthcheckusespg_isreadyto verify Postgres is accepting connections postgres_datavolume persists the database between container restarts- Port
5432is exposed so you can connect with TablePlus, pgAdmin, orpsqlfrom your host
Connect in Node.js (using pg):
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
3. Node.js + PostgreSQL + Redis
Add Redis for caching, sessions, or queues.
docker-compose.yml:
services:
app:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: development
DATABASE_URL: postgresql://postgres:password@db:5432/myapp
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ./src:/app/src
command: npm run dev
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
ports:
- "6379:6379"
volumes:
postgres_data:
redis_data:
Connect Redis in Node.js (using ioredis):
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
// Cache example
async function getCachedUser(id) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.query('SELECT * FROM users WHERE id = $1', [id]);
await redis.setex(`user:${id}`, 3600, JSON.stringify(user.rows[0]));
return user.rows[0];
}
4. Development with Hot Reload
For a smooth dev experience with instant code updates:
Dockerfile.dev:
FROM node:20-alpine
WORKDIR /app
# Install nodemon globally for hot reload
RUN npm install -g nodemon
COPY package*.json ./
RUN npm install # include devDependencies for development
EXPOSE 3000
# Default command β override in docker-compose
CMD ["nodemon", "src/index.js"]
docker-compose.dev.yml:
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
- "9229:9229" # Node.js debugger port
environment:
NODE_ENV: development
DATABASE_URL: postgresql://postgres:password@db:5432/myapp
volumes:
- ./src:/app/src # sync source code
- ./package.json:/app/package.json
- /app/node_modules # anonymous volume β don't sync host node_modules
command: nodemon --inspect=0.0.0.0:9229 src/index.js
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:
Run the dev setup:
docker compose -f docker-compose.dev.yml up --build
Critical: The anonymous volume for node_modules
volumes:
- ./src:/app/src
- /app/node_modules # This line is essential
Without /app/node_modules, Docker overwrites the containerβs node_modules with your hostβs (which may have different native bindings). The anonymous volume protects it.
5. Multi-Stage Build for Production
Separate development and production builds to keep the production image lean:
Dockerfile:
# ---- Dependencies ----
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# ---- Build ----
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # for TypeScript or bundled apps
# ---- Production ----
FROM node:20-alpine AS production
WORKDIR /app
# Security: run as non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json .
EXPOSE 3000
CMD ["node", "dist/index.js"]
docker-compose.prod.yml:
services:
app:
build:
context: .
target: production
restart: unless-stopped
ports:
- "3000:3000"
environment:
NODE_ENV: production
DATABASE_URL: ${DATABASE_URL}
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
Use environment files:
# .env.prod
DATABASE_URL=postgresql://user:strongpassword@db:5432/myapp
POSTGRES_DB=myapp
POSTGRES_USER=user
POSTGRES_PASSWORD=strongpassword
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d
6. Node.js + MongoDB
For apps using MongoDB instead of PostgreSQL:
services:
app:
build: .
ports:
- "3000:3000"
environment:
MONGODB_URI: mongodb://mongo:27017/myapp
depends_on:
- mongo
mongo:
image: mongo:7
restart: unless-stopped
volumes:
- mongo_data:/data/db
ports:
- "27017:27017"
volumes:
mongo_data:
Useful Docker Compose Commands
# Start all services (build if needed)
docker compose up --build
# Start in detached mode (background)
docker compose up -d
# Stop all services
docker compose down
# Stop and remove volumes (reset databases)
docker compose down -v
# View logs
docker compose logs -f app
docker compose logs -f db
# Run a command in a running container
docker compose exec app sh
docker compose exec db psql -U postgres myapp
# Rebuild only one service
docker compose up --build app
# Scale a service
docker compose up --scale worker=3
Troubleshooting
βConnection refusedβ on startup
Your app is starting before the database is ready. Use depends_on with service_healthy and a proper healthcheck.
Changes to node_modules not taking effect
Rebuild the image:
docker compose build --no-cache app
Port already in use
# Find what's using the port
lsof -i :5432
# Or change the host port in docker-compose.yml
ports:
- "5433:5432" # host port 5433 maps to container 5432
Database data lost after docker compose down
Named volumes persist by default. Use docker compose down -v only when you want to reset. Check that youβre using a named volume (e.g., postgres_data) not an anonymous bind mount.
Environment variables not loading
Docker Compose automatically loads a .env file from the same directory. For other files:
docker compose --env-file .env.local up
Production Checklist
Before deploying a Docker Compose setup to production:
- Use a non-root user in your Dockerfile
- Set
restart: unless-stoppedon all services - Never hardcode passwords β use environment variables or Docker secrets
- Use named volumes for all persistent data
- Add
healthcheckto all stateful services - Use multi-stage builds to minimize image size
- Pin image versions (
postgres:16-alpine, notpostgres:latest) - Set resource limits for containers
Download the Full Boilerplate
The Full-Stack Boilerplate Collection includes a production-ready Express API starter with Docker Compose, PostgreSQL, Redis, JWT auth, and CI/CD already configured. Skip the setup and start building.
Related tools:
- DevPlaybook API Tester β Test your containerized API endpoints
- DevPlaybook Cron Generator β Generate cron expressions for scheduled jobs
- DevPlaybook API Request Builder β Build and test complex API requests
Free Newsletter
Level Up Your Dev Workflow
Get new tools, guides, and productivity tips delivered to your inbox.
Plus: grab the free Developer Productivity Checklist when you subscribe.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links β we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.