🐳 Docker Guide for Node.js & Postgres

node modules generally should be in a volume as they have OS specific binaries so cant copy from host to container directly

  • volume mapping -> can lead to hot reload??

This guide covers two workflows:

  • Lite Setup: Only the Database is in Docker (Best for local dev)
  • Full Setup: Both App and Database are in Docker (Best for production simulation)

Use this for daily development. Your Node app runs fast on your laptop, while your DB runs cleanly in a container.

1.1 docker-compose.yml

Create this file in your project root:

version: "3.8"

services:
  db:
    image: postgres:16-alpine
    container_name: my_local_db
    restart: always
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - ./pg-data:/var/lib/postgresql/data

1.2 Environment (.env)

Since the App is on your laptop, it connects to localhost.

DB_USER=myuser
DB_PASSWORD=mypassword
DB_NAME=mydatabase
DATABASE_URL="postgres://myuser:mypassword@localhost:5432/mydatabase"

1.3 Commands

Goal Command
Start DB docker-compose up -d
Stop DB docker-compose down
Wipe Data (Reset) docker-compose down then delete pg-data/ folder
Run App npm run dev (Normal local terminal)

for first time setup, run:

docker-compose up -d --build

v

💒 Part 2: The β€œFull” Setup

Use this to test how your app behaves in a Linux environment or for easy deployment.

2.1 Dockerfile

Create this file to define your Node image.

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

2.2 docker-compose.yml (App + DB)

version: "3.8"

services:
  db:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - ./pg-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
    volumes:
      - .:/app
      - /app/node_modules

πŸ’‘ Part 3: Tips & Tricks

3.1 Managing Secrets

  • Never commit .env to GitHub. Add it to .gitignore.
  • Create a .env.example with dummy values for teammates.
  • Docker Compose automatically reads variables from the .env file.

3.2 Installing Packages (Full Setup Only)

If your app is running inside Docker, follow this 2-step process:

Install Inside (For the App):

docker-compose exec app npm install axios

Sync Outside (For VS Code):

npm install

open a shell

docker-compose exec app sh

3.3 Running Scripts

Database Migration:

docker-compose exec app npm run db:migrate

Database Seed:

docker-compose exec app npm run db:seed

Open a Shell:

docker-compose exec app sh

3.4 Git & Lockfiles

  • Trust the Docker Lockfile: Docker generates Linux-compatible lockfiles.
  • Prefer lockfiles generated by docker-compose exec app npm install.
  • On deployment servers, use npm ci instead of npm install.

3.5 Troubleshooting Networking

Error: connect ECONNREFUSED 127.0.0.1:5432

  • Cause: Trying to connect to localhost from inside a container.
  • Fix: Change hostname to db (the service name).

Error: Module not found

  • Cause: Installed package locally but not in the container.
  • Fix: docker-compose exec app npm install

βœ… Final Checklist: Which Setup to Use?

Aspect Lite Setup Full Setup
Speed ⚑️ Fast (Native) 🐒 Slower (Virtualization)
Complexity 🟒 Easy πŸ”΄ Moderate
Best For Daily Coding / Solo Dev Testing Linux / Teams
Host localhost db

🎯 Part 3: Advanced Configurations

3.1 Using Profiles for Optional Services

If you need tools like pgAdmin only for debugging, use Docker Compose Profiles:

version: "3.8"

services:
  db:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - ./pg-data:/var/lib/postgresql/data

  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db

  pgadmin:
    image: dpage/pgadmin4
    profiles: ["debug"]
    ports:
      - "5050:80"
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: admin

Run normally:

docker-compose up

Run with debug tools:

docker-compose --profile debug up

πŸ“‹ Quick Reference: Which Method to Use?

Scenario Method Command
Local Development Lite Setup docker-compose up -d
Production Testing Full Setup docker-compose up
Optional Tools Profiles docker-compose --profile debug up

🎯 Part 4: Common Add-ons

4.1 Database GUI (pgAdmin)

The Problem: You want to see your data without installing heavy desktop apps like DBeaver or TablePlus.

The Docker Solution: Run a web-based viewer alongside your database.

Add this to your docker-compose.yml:

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}

  pgadmin:
    image: dpage/pgadmin4
    ports:
      - "8080:80"
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@admin.com
      PGADMIN_DEFAULT_PASSWORD: root
    depends_on:
      - db

Then visit http://localhost:8080 to manage your database.

4.2 Caching (Redis)

The Problem: Installing Redis on Windows is unsupported, and on Mac it runs in the background consuming RAM.

The Docker Solution: Add 4 lines to your compose file for a disposable Redis instance.

services:
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"

Usage in Node:

const { createClient } = require("redis");
const client = createClient({ url: "redis://localhost:6379" });

This site uses Just the Docs, a documentation theme for Jekyll.