← Kembali ke Blog

Docker Compose buat Local Development: Setup Node.js + PostgreSQL + Redis dari Nol

Kebanyakan developer pernah di posisi ini. Clone repo, install Node, Postgres, Redis, dan semua yang diminta README. Setengah jam kemudian masih debugging kenapa koneksi database ditolak, atau kenapa port Redis bentrok dengan sesuatu yang sudah jalan di mesin. Terus pull branch baru, dan siklusnya mulai lagi.

Docker Compose solve masalah ini. Satu file, satu command, semua service spinning up secara identik di mesin setiap developer. Nggak ada lagi "works on my machine."

Prerequisites

  • Docker Engine 24+ dan Docker Compose v2 (cek dengan docker version dan docker compose version)
  • Sebuah Node.js project (project baru juga bisa)
  • Familiar dengan terminal

Struktur Project

Mulai dari Node.js app simpel yang connect ke PostgreSQL dan Redis:

my-app/
  src/
    index.js
  package.json
  docker-compose.yml
  .env
  .dockerignore
{
  "name": "my-app",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js"
  },
  "dependencies": {
    "express": "^4.21.0",
    "pg": "^8.13.0",
    "ioredis": "^5.4.0"
  }
}
// src/index.js
const express = require('express');
const { Pool } = require('pg');
const Redis = require('ioredis');

const app = express();
const redis = new Redis({ host: 'redis', port: 6379 });
const pool = new Pool({
  host: 'postgres',
  port: 5432,
  database: process.env.DB_NAME || 'myapp',
  user: process.env.DB_USER || 'postgres',
  password: process.env.DB_PASSWORD || 'postgres',
});

app.get('/', async (req, res) => {
  await redis.set('hits', (await redis.get('hits') || 0) + 1);
  const hits = await redis.get('hits');
  const dbResult = await pool.query('SELECT NOW()');
  res.json({
    message: 'Hello from Docker Compose!',
    hits,
    serverTime: dbResult.rows[0].now,
  });
});

app.listen(3000, () => console.log('Running on port 3000'));

Perhatikan database host-nya postgres, bukan localhost. Di dalam Docker Compose, service satu sama lain berkomunikasi pakai nama service. Kalau kamu pakai localhost, app bakal connect ke dirinya sendiri dan gagal.

docker-compose.yml

Ini file lengkapnya, nanti kita bedah satu per satu:

services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./src:/app/src
    environment:
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_NAME=myapp
      - DB_USER=postgres
      - DB_PASSWORD=postgres
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - devnet

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - devnet

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

volumes:
  pgdata:

networks:
  devnet:

Penjelasan tiap section

Service app. Build Dockerfile (dijelaskan di bawah), map port 3000, dan mount ./src ke dalam container. Volume mount inilah yang bikin hot reload jalan. Edit file di local, dan process yang running di dalam container langsung nangkep perubahannya.

Service postgres. Pakai official image Alpine. Section volumes nyimpen data di antara restart, jadi kamu nggak kehilangan database tiap kali docker compose down. Healthcheck memastikan app nggak coba connect sebelum Postgres siap.

Service redis. Pola yang sama. Image Alpine ringan, healthcheck pakai redis-cli ping.

depends_on dengan condition. Ini bagian kuncinya. Tanpa condition: service_healthy, Docker cuma cek apakah container sudah start, bukan apakah service di dalamnya benar-benar menerima koneksi. App kamu bakal coba connect, gagal, dan crash.

Dockerfile

Kamu butuh Dockerfile untuk service app:

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

Nggak pakai multi-stage build di sini. Ini local development, bukan production. Kita tetap simpel. Flag --watch di script dev me-restart Node process saat file berubah.

Tambah .dockerignore supaya file yang nggak perlu nggak ikut ke build context:

node_modules
npm-debug.log
.git
.env
.env.*

Environment Variable

Simpan secret di luar docker-compose.yml. Pakai file .env di root project:

# .env
DB_HOST=postgres
DB_PORT=5432
DB_NAME=myapp
DB_USER=postgres
DB_PASSWORD=postgres

Docker Compose otomatis baca file ini dan inject nilainya ke service yang referensinya pakai syntax ${VARIABLE_NAME}. Kalau nggak mau pakai file .env, kamu bisa set variabelnya inline di docker-compose.yml atau export di shell.

Jalankan Stack-nya

# Build dan start semuanya
docker compose up --build

# Jalankan di background
docker compose up --build -d

# Lihat log
docker compose logs -f app

# Stop semuanya
docker compose down

# Stop dan hapus volume (database fresh)
docker compose down -v

Jalankan docker compose up --build untuk pertama kali. Start berikutnya tanpa perubahan code di Dockerfile skip step build. Tambah -d buat jalanin semua di background.

docker compose down -v hapus database. Berguna kalau kamu butuh clean slate. Tanpa -v, data kamu persist di antara restart.

Hot Reload dalam Aksi

Volume mount ./src:/app/src yang ngurusin beratnya di sini. Saat kamu edit src/index.js di host, perubahannya langsung terrefleksi di dalam container. Karena dev script pakai node --watch, Node detect perubahan file dan restart.

Coba ini:

  1. Start stack dengan docker compose up
  2. Buka src/index.js
  3. Ganti response message
  4. Simpan file
  5. Hit http://localhost:3000 lagi

Response baru muncul tanpa restart container manapun. Full cycle rebuild-and-restart cuma butuh waktu kurang dari satu detik.

Connect ke Service dari Host

Mapping ports bikin service accessible dari luar Docker. Connect ke Postgres dari pgAdmin, Tableau, atau psql di local:

psql -h localhost -p 5432 -U postgres -d myapp

Begitu juga untuk Redis:

redis-cli -h localhost -p 6379

Ini berguna untuk debugging. Kamu bisa inspeksi state database sambil app running tanpa perlu install Postgres atau Redis di mesin.

Kesalahan yang Sering Muncul

Container start tapi app crash dengan connection refused. App mencoba connect ke localhost atau 127.0.0.1. Di dalam container, localhost merujuk ke container itu sendiri, bukan host atau container lain. Pakai nama service (postgres, redis) sebagai hostname.

Port sudah dipakai. Kalau Postgres atau Redis sudah jalan di host, mapping ports gagal. Stop service local atau ganti host port: "5433:5432 map port 5432 di container ke port 5433 di host.

Build pertama lambat. Docker download base image di run pertama. Build berikutnya pakai cache dan jauh lebih cepat.

Data hilang setelah docker compose down. Tanpa named volume (kayak pgdata), data disimpan di writable layer container dan terhapus saat container di-remove. Section volumes di service Postgres mencegah ini.

Health check gagal. Kalau pg_isready atau redis-cli ping return error, dependent service nggak pernah start. Pastikan database name dan user di healthcheck match dengan nilai POSTGRES_DB dan POSTGRES_USER.

Memperluas Setup

Setelah dasarnya jalan, pertimbangkan tambahan ini:

  • Tambah worker service (misal Bull queue consumer) dengan copy konfigurasi app dan ganti CMD
  • Mount sertifikat TLS untuk service yang butuh
  • Tambah reverse proxy kayak Nginx atau Caddy di depan app buat HTTPS lokal
  • Pakai Docker Compose profiles buat optional jalankan extra service: docker compose --profile debug up

Kapan Harus Berhenti Pakai Ini

Docker Compose works well untuk local development dan deployment sederhana. Dia nggak handle orkestrasi, auto-scaling, atau rolling update. Kalau kamu butuh fitur-fitur itu, pindah ke Kubernetes atau managed platform. Tapi untuk daily development, Compose susah dikalahkan soal kesederhanaan.

Referensi

Butuh Bantuan Implementasi?

Saya membantu tim mendesain dan membangun infrastruktur cloud scalable, pipeline DevOps, dan sistem production-grade.

Konsultasi Gratis