DeployEasy
DockerIntermediate

Docker Compose in Production: Node.js, PostgreSQL, and Backups

Build a safer Docker Compose production stack for Node.js and PostgreSQL with health checks, private networks, volumes, secrets, log rotation, backups, and rollback.

· 3 min read· 528 words
Table of contents

Docker Compose is useful for a small VPS, but a production Compose file needs more than docker compose up. The application, database, storage, logs, health checks, and recovery plan should be designed together.

A safer Compose baseline

services:
  api:
    image: registry.example.com/myapp:${APP_VERSION}
    restart: unless-stopped
    env_file: .env.production
    depends_on:
      db:
        condition: service_healthy
    networks: [internal]
    ports:
      - "127.0.0.1:3000:3000"
    healthcheck:
      test: ["CMD", "wget", "--spider", "--quiet", "http://127.0.0.1:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks: [internal]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user -d app"]
      interval: 10s
      timeout: 5s
      retries: 5

networks:
  internal:

volumes:
  postgres_data:

The exact health command depends on the image. Keep it lightweight and make sure the image actually contains the command you call.

Keep the database private

Do not publish PostgreSQL’s 5432 port to the internet unless an external client genuinely needs it and access is restricted. The API can reach db:5432 over the internal Compose network.

Treat volumes as data, not backups

The named volume keeps the database across container replacement. It is not a backup. Back up the logical database and important files to storage outside the VPS or provider account:

docker compose exec -T db pg_dump -U app_user -d app > /var/backups/app-$(date +%F).sql

Encrypt backups where appropriate, restrict their permissions, and test restoring them on an isolated instance.

Handle secrets safely

Do not commit .env.production, passwords, or API tokens. Use a secret manager, encrypted CI variables, or a protected file deployed out of band:

chmod 600 .env.production

Remember that docker compose config and process inspection can expose interpolated secrets.

Rotate logs and monitor disk

An application that logs indefinitely can fill a VPS. Configure Docker’s logging driver or use a central collector. At minimum, monitor disk and inode usage:

docker system df
df -h
df -i

Avoid pruning images or deleting volumes automatically without understanding what the deployment needs.

Deploy an immutable image

Build and scan an image in CI, tag it with a commit SHA, push it to a registry, and deploy that exact tag:

docker compose pull api
docker compose up -d --no-build api
docker compose ps

Avoid using only latest when you need to identify or roll back a release.

Migrations and rollback

Run database migrations as an explicit release step. Prefer backward-compatible changes when old and new application instances may overlap.

docker compose run --rm api npm run migrate
docker compose up -d api
curl --fail http://127.0.0.1:3000/health

If the new image fails, return to the previous immutable tag and inspect the failed migration. A database rollback may require a backup restore; it is not always equivalent to rolling back application code.

Production checklist

  • The database has a persistent volume and an off-host backup.
  • Backup restore has been tested.
  • Only the reverse proxy is publicly exposed.
  • Services restart after a host reboot.
  • Health checks represent useful readiness.
  • Logs have retention limits and alerts.
  • Secrets are outside Git and protected on disk.
  • Images have immutable release tags.
  • Deployment and rollback commands are documented.

Continue reading