Skip to content

10. # Docker ComposeΒΆ

  • its multi container concept
  • Docker Compose is a tool you can use to define and share multi-container applications. This means you can run a project with multiple containers using a single source.
  • docker-compose.yml is a YML file defining services, networks, and volumes for a Docker container
  • tool for defining and running multi-container applications. These tools integrate tightly with code repositories (such as GitHub) and continuous integration and continuous delivery (CI/CD) pipeline tools (such as Jenkins).
  • docker compose build - to build our multi-container using docker build
  • docker-compose build --no-cache ==> downloads images everytime no local reuse
  • docker compose up - to run the containers [this will not look for change in docker file if images are already present hence we use below command ]
  • docker-compose up --build => Rebuilds images with latest changes in code
  • docker-compose up -d ==> ups in detached mode

  • docker compose ps - to check container list.

  • docker compose ps -a -
  • docker-compose logs - to see logs of running services
    • docker-compose logs => to see logs of specific service
  • docker logs => view logs
  • docker logs -f => Follow logs
  • docker-compose ps => list containers
  • docker-compose up --scale worker=5 => Scaling Services
  • docker-compose exec app sh => Executing Commands Inside Services
    • curl localhost - > gives error of curl not found if curl is not installed
    • exit and edit DockerFIle , to add curl with APK
    • RUN api add --update curl
    • docker-compose up -d but it will still give you same error because it didn't rebuild image
    • docker-compse up -d --build ==> this will rebuild image
    • now if you try docker-compose exec web sh ==> curl localhost ==> it will give error
  • docker-compose.override.yml = > Overrides are great for dev-mode mounts.
  • docker-compose exec service sh
  • docker-compose config
  • docker-compsoe down --help ==> gives you options to delete images and other things
  • docker-compose down -v => Cleaning Up Compose Resources

  • docker-compose stop - to stop all services

  • docker-compose down --volumes -> o bring everything down, removing the containers entirely, with the data volume of the services:

5. Services SectionΒΆ

Each service defines a container.

Example:

services:

api:

image: node:18

command: ["npm", "start"]


12. HealthchecksΒΆ

healthcheck:

test: ["CMD", "curl", "-f", "http://localhost:3000"]

interval: 10s

retries: 5

13. Restart PoliciesΒΆ

restart: always

Below is a complete ready-to-copy Markdown article containing:

βœ” Explanation
βœ” Full server.js
βœ” Multi-stage Dockerfile
βœ” docker-compose.dev.yml
βœ” docker-compose.prod.yml
βœ” Directory structure
βœ” How to run Dev & Prod

You can paste this directly in your blog.


🐳 Building a Production-Ready Multi-Container Express App (Redis + Postgres + Docker)¢

In this guide, we will create a fully production-ready Node.js Express app using:

  • Redis – for caching & fast counter increments

  • Postgres – for persistent storage

  • Docker Compose – multi-container orchestration

  • Multi-stage Dockerfile – optimized dev & production images

  • Healthchecks – ensuring app starts only when DB + Redis are ready

  • Graceful shutdown

  • Retry logic with exponential backoff

  • Automatic database migrations

You can copy the entire setup below and run it as-is.


πŸ“ Project StructureΒΆ

/your-app
  β”œβ”€β”€ server.js
  β”œβ”€β”€ package.json
  β”œβ”€β”€ Dockerfile
  β”œβ”€β”€ docker-compose.dev.yml
  β”œβ”€β”€ docker-compose.prod.yml

πŸš€ 1. Express Server with Redis + PostgresΒΆ

This server.js includes:

βœ” Redis connection with retry + backoff
βœ” Postgres connection with retry + backoff
βœ” Graceful shutdown
βœ” Auto migrations
βœ” Increment API endpoint


server.jsΒΆ

const express = require("express");
const { Client } = require("pg");
const Redis = require("ioredis");

const PORT = process.env.PORT || 3000;
const POSTGRES_URL = process.env.POSTGRES_URL;
const REDIS_URL = process.env.REDIS_URL;

const app = express();
app.use(express.json());

// ------------------------------
// Retry Helper (Exponential Backoff)
// ------------------------------
async function retry(fn, retries = 10, delay = 1000) {
  try {
    return await fn();
  } catch (err) {
    if (retries === 0) throw err;
    console.log(`Retry failed. Retrying in ${delay}ms...`);
    await new Promise(res => setTimeout(res, delay));
    return retry(fn, retries - 1, delay * 2);
  }
}

// ------------------------------
// Postgres Connection
// ------------------------------
const pg = new Client({
  connectionString: POSTGRES_URL,
});

async function connectPostgres() {
  await retry(async () => {
    console.log("Connecting to Postgres...");
    await pg.connect();
    console.log("Connected to Postgres!");
  });
}

// ------------------------------
// Redis Connection
// ------------------------------
let redis;

async function connectRedis() {
  await retry(async () => {
    console.log("Connecting to Redis...");
    redis = new Redis(REDIS_URL);
    await redis.ping();
    console.log("Connected to Redis!");
  });
}

// ------------------------------
// Migrations
// ------------------------------
async function runMigrations() {
  console.log("Running migrations...");

  await pg.query(`
    CREATE TABLE IF NOT EXISTS counters (
      id VARCHAR(50) PRIMARY KEY,
      value INTEGER NOT NULL
    );
  `);

  console.log("Migrations complete.");
}

// ------------------------------
// API Route
// ------------------------------
app.get("/increment", async (req, res) => {
  const key = "counter1";

  let newValue = await redis.incr(key);

  await pg.query(
    `
      INSERT INTO counters (id, value)
      VALUES ($1, $2)
      ON CONFLICT (id) DO UPDATE SET value = $2
    `,
    [key, newValue]
  );

  res.json({ counter: newValue });
});

// ------------------------------
// Graceful Shutdown
// ------------------------------
function gracefulShutdown() {
  console.log("Shutting down gracefully...");
  redis && redis.quit();
  pg && pg.end();
  server.close(() => {
    console.log("HTTP server closed.");
    process.exit(0);
  });
}

process.on("SIGINT", gracefulShutdown);
process.on("SIGTERM", gracefulShutdown);

// ------------------------------
// Startup Sequence
// ------------------------------
async function start() {
  await connectRedis();
  await connectPostgres();
  await runMigrations();

  global.server = app.listen(PORT, () =>
    console.log(`Server running on ${PORT}`)
  );
}

start().catch(err => {
  console.error("Startup failed:", err);
  process.exit(1);
});

πŸ› οΈ 2. Multi-Stage Dockerfile (Dev + Prod)ΒΆ

This Dockerfile builds:

  • Dev image (with live reload)

  • Production image (optimized)


DockerfileΒΆ

# -------------------------
# BASE IMAGE (Shared)
# -------------------------
FROM node:18-alpine AS base
WORKDIR /app
ENV NODE_ENV=production

# -------------------------
# DEVELOPMENT STAGE
# -------------------------
FROM base AS development
ENV NODE_ENV=development

COPY package*.json ./
RUN npm install

COPY . .
CMD ["npm", "run", "dev"]

# -------------------------
# PRODUCTION BUILD
# -------------------------
FROM base AS build

COPY package*.json ./
RUN npm install --only=production
COPY . .

# -------------------------
# PRODUCTION RUNTIME
# -------------------------
FROM node:18-alpine AS production
WORKDIR /app
ENV NODE_ENV=production

COPY --from=build /app /app

EXPOSE 3000
CMD ["node", "server.js"]

🐳 3. Docker Compose (Development)¢

This is meant for local development:

  • Hot reloading

  • Mounted volumes

  • Healthchecks

  • Node runs via nodemon


docker-compose.dev.ymlΒΆ

version: "3.8"

services:
  app:
    build:
      context: .
      target: development
    container_name: app_dev
    ports:
      - "3000:3000"
    environment:
      - REDIS_URL=redis://redis:6379
      - POSTGRES_URL=postgres://postgres:postgres@postgres:5432/appdb
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
    volumes:
      - .:/app
      - /app/node_modules
    command: ["npm", "run", "dev"]

  redis:
    image: redis:7
    container_name: redis_dev
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  postgres:
    image: postgres:15
    container_name: pg_dev
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: appdb
    ports:
      - "5432:5432"
    volumes:
      - pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 10

volumes:
  pg-data:

🏭 4. Docker Compose (Production)¢

Production:

  • No volumes

  • Lean build

  • Healthchecks

  • Clean image


docker-compose.prod.ymlΒΆ

version: "3.8"

services:
  app:
    build:
      context: .
      target: production
    container_name: app_prod
    ports:
      - "3000:3000"
    environment:
      - REDIS_URL=redis://redis:6379
      - POSTGRES_URL=postgres://postgres:postgres@postgres:5432/appdb
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy

  redis:
    image: redis:7
    container_name: redis_prod
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  postgres:
    image: postgres:15
    container_name: pg_prod
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: appdb
    volumes:
      - pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 10

volumes:
  pg-data:

▢️ Running the AppΒΆ

Development with Hot ReloadingΒΆ

docker compose -f docker-compose.dev.yml up --build

Hit the API:

GET http://localhost:3000/increment

Production ModeΒΆ

docker compose -f docker-compose.prod.yml up --build -d

πŸŽ‰ Final NotesΒΆ

This setup gives you:

  • Containerized Express API

  • Redis caching layer

  • Postgres persistence

  • Health-checked startup

  • Automatic migrations

  • Hot reload for development

  • Minimal production images

  • Graceful shutdown safety

You can deploy this in:

βœ” Docker Swarm
βœ” Kubernetes
βœ” AWS ECS
βœ” Any Linux server