E-NO Logo
EN FR
Docker Compose 7 Min Read

Docker Compose for local production-like development workflows: practical implementation guide

calendar_today Published: 2026-07-08
update Last Updated: 2026-07-08
analytics SEO Efficiency: 97%
Technical guide illustration for Docker Compose for local production-like development workflows: practical implementation guide.

Intro

A production-like local environment pays for itself quickly. When your app, database, cache, and proxy run together under Docker Compose with realistic settings, you reveal integration issues early, debug faster, and reduce surprises later. This guide shows how to build that setup with services, networks, volumes, environment files, health checks, and a practical debugging workflow.

Workflow Overview

Use a simple loop that mirrors how software is delivered:

  1. Plan
  • List services and how they talk (ports, protocols, env vars).
  • Decide networks, volumes, and health criteria for each service.
  1. Build
  • Create production-like Dockerfiles (small images, non-root users, pinned versions).
  • Use docker build or let Compose build from your Dockerfile.
  1. Run
  • Start the stack with docker compose up -d.
  • Wait for health checks to go healthy.
  1. Test
  • Send traffic through the public entry point (for example, Nginx on port 80).
  • Validate readiness, liveness, and core use cases.
  1. Iterate
  • Change configs or code, rebuild, and rerun.

This clear separation of steps helps keep changes understandable and reduces rework.

Compose Design

Below is a practical docker-compose.yml for a small web stack: a Node.js app, Postgres, Redis, and Nginx as the edge proxy. It uses two networks (frontend and backend), named volumes for data, env files for settings, and health checks with startup ordering.

version: '3.9'

services:
  web:
    build:
      context: ./web
    image: local/web: dev
    ports:
      - '8080:3000'
    env_file:
      - ./env/web.env
    environment:
      - NODE_ENV=development
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ['CMD-SHELL', 'wget -qO- http://localhost:3000/health || exit 1']
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 15s
    volumes:
      - ./web:/app: cached
      - web-node-modules:/app/node_modules
    networks:
      - frontend
      - backend

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: appdb
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U app -d appdb']
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  redis:
    image: redis:7-alpine
    command: ['redis-server', '--appendonly', 'yes']
    volumes:
      - redis-data:/data
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 10s
      timeout: 3s
      retries: 5
    networks:
      - backend

  nginx:
    image: nginx:1.25-alpine
    depends_on:
      web:
        condition: service_healthy
    ports:
      - '80:80'
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d: ro
    networks:
      - frontend

volumes:
  db-data:
  redis-data:
  web-node-modules:

networks:
  frontend:
  backend:

Nginx config example

Route traffic on port 80 to the web service.

# ./nginx/conf.d/app.conf
server {
  listen 80;
  server_name _;

  location / {
    proxy_pass http://web:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  location /healthz {
    return 200 'ok';
    add_header Content-Type text/plain;
  }
}

Node.js Dockerfile example

Keep it close to production: minimal base, non-root, pinned version, and a simple health endpoint in your app at /health.

# ./web/Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN addgroup -S app && adduser -S app -G app && chown -R app: app /app
USER app
EXPOSE 3000
CMD node server.js

Minimal Node app example

// ./web/server.js
const express = require('express');
const app = express();
const port = 3000;

app.get('/health', (req, res) => res.send('ok'));
app.get('/', (req, res) => res.send('Hello from web via Nginx'));

app.listen(port, () => console.log(`web listening on ${port}`));

Env and Secrets

Use a project-level .env file for Compose variables, and per-service env files for app settings.

  • Project .env controls Compose behavior and shared values.
  • Service env files control application settings.
  • Do not commit real secrets. Provide sample files and load real values from a secure location.

Example project .env:

# ./.env
COMPOSE_PROJECT_NAME=prodlike
WEB_PORT=8080

Example service env file:

# ./env/web.env
PORT=3000
DATABASE_URL=postgresql://app: app@db:5432/appdb
REDIS_URL=redis://redis:6379/0

You can reference ${WEB_PORT} in your compose file if you prefer to avoid hardcoding ports.

Health Checks and Order

Health checks make dependency readiness explicit. Use healthcheck in each service and depends_on with condition: service_healthy in dependents. This prevents the web from failing because the database is not ready yet.

  • Web health: GET /health returns 200.
  • Postgres health: pg_isready passes.
  • Redis health: redis-cli ping returns PONG.

Compose will show health in docker compose ps so you can see when the stack is ready.

Bring it up, test it, and iterate

Start the stack:

docker compose up -d --build

Check status and health:

docker compose ps
docker compose logs -f --tail=100 web db redis nginx

Test through the proxy like a user would:

curl -sS http://localhost/

Hot-reload options:

  • Bind-mount ./web into the container for rapid local edits.
  • If you need nodemon, add it to devDependencies and run it with an override compose file only for local use.

Rebuild when Dockerfile changes:

docker compose build web && docker compose up -d web

Debugging and Troubleshooting

Common checks and fixes:

  • Port conflicts
  • Symptom: docker compose up fails to expose a port.
  • Fix: change host ports (e.g., WEB_PORT), or stop the conflicting process.
  • Health check keeps failing
  • Check container logs: docker compose logs web.
  • Exec into the container and curl the health endpoint locally: docker compose exec web sh -lc 'wget -qO- http://localhost:3000/health'.
  • Verify env variables are set: docker compose exec web env | sort.
  • App cannot reach db or redis
  • Verify DNS within the network: docker compose exec web getent hosts db redis.
  • Test TCP reachability: docker compose exec web sh -lc 'nc -zv db 5432' and 'nc -zv redis 6379'.
  • Volume permissions on Linux
  • Symptom: EACCES on write.
  • Fix: set the same UID/GID inside the container as your host user, or chown the workdir during build as shown in the Dockerfile.
  • Slow file sync on macOS/Windows
  • Symptom: slow hot reload.
  • Fix: use :cached option, move heavy dependencies to a named volume (web-node-modules), or build images more often instead of live-mounting everything.
  • Data resets unexpectedly
  • Ensure you use named volumes (db-data, redis-data) rather than anonymous volumes.
  • Removing with docker compose down -v will delete named volumes; use with care.
  • Environment not applied
  • Check resolution order: .env, shell, and env_file.
  • Print env: docker compose config to see the final merged config.

Useful commands:

docker compose config
docker compose ps --format json | jq '.'
docker compose logs -f web
docker compose exec -it web sh
docker compose inspect web

# Networks and volumes
docker network ls
docker network inspect prodlike_backend
docker volume ls

Local Pilot Plan

Start small, measure, then expand.

Scope

  • Services: web + db only.
  • Success criteria:
  • Time from docker compose up -d to both services healthy is under 15 seconds on your machine.
  • curl http://localhost/ returns 200 and expected content.
  • docker compose restart web recovers to healthy within 5 seconds.

Steps

  1. Implement health endpoints and checks for web and db.
  2. Add a named volume for Postgres data.
  3. Wire env via ./env/web.env and ./.env.
  4. Measure baseline timings with the commands in this guide.
  5. Add Redis and Nginx when the pilot meets the success criteria.

Why this works

  • Narrow scope is easier to debug and reason about.
  • Clear, measurable outcomes tell you when to grow the stack.

Conclusion

A production-like Docker Compose setup gives you fast, realistic feedback without leaving your laptop. Model your stack with services, networks, and volumes; wire configuration through env files; and use health checks and a repeatable workflow to reduce rework. Start with a narrow pilot, measure results, and expand toward full parity.

Article Quality Score

Reader usefulness 97%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL