Local Development #

Local development adalah use case paling umum dan paling valuable dari Docker Compose. Dengan satu file docker-compose.yml, setiap developer bisa menjalankan stack lengkap aplikasimu dalam hitungan menit — tanpa harus install Postgres, Redis, atau service lain secara manual.

Artikel ini membahas pattern dan best practice untuk setup local development yang efektif.

Pattern Dasar #

File docker-compose.yml untuk local development biasanya punya karakteristik:

  • Service gambar production (Postgres, Redis, dll.) — bukan mock.
  • Source code di-bind mount dari host ke container — untuk hot reload.
  • Override file (opsional) untuk konfigurasi development-specific.
  • Seed data untuk testing.
# docker-compose.yml
services:
  api:
    build:
      context: ./api
      target: development  # multi-stage build
    command: npm run dev
    volumes:
      - ./api/src:/app/src:ro  # source code, read-only
      - api-node-modules:/app/node_modules  # avoid override
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgres://app:dev@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
  
  web:
    build: ./web
    command: npm run dev
    volumes:
      - ./web/src:/app/src
    ports:
      - "5173:5173"
    depends_on:
      - api
  
  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=dev
      - POSTGRES_DB=myapp
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 10s
      timeout: 5s
      retries: 5
  
  cache:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

volumes:
  db-data:
  api-node-modules:

Override File untuk Development #

Untuk base yang sama dipakai untuk production, pisahkan dengan override file.

# docker-compose.yml (base — sama untuk semua environment)
services:
  api:
    build: ./api
    environment:
      - DATABASE_URL=postgres://app:prod@db:5432/myapp

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=prod
      - POSTGRES_DB=myapp

# docker-compose.override.yml (default-loaded untuk development)
services:
  api:
    command: npm run dev  # override production command
    volumes:
      - ./api/src:/app/src  # hot reload
    environment:
      - NODE_ENV=development
    ports:
      - "3000:3000"
  
  db:
    environment:
      - POSTGRES_PASSWORD=dev
    ports:
      - "5432:5432"

Override file otomatis di-load saat docker compose up tanpa flag -f. Untuk production, jalankan tanpa override atau dengan override yang berbeda.

Hot Reload #

Hot reload adalah fitur yang memungkinkan perubahan source code di host langsung terlihat di container, tanpa rebuild atau restart.

Node.js dengan nodemon:

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
// package.json
{
  "scripts": {
    "dev": "nodemon --watch src --ext js,json src/index.js"
  },
  "devDependencies": {
    "nodemon": "^3.0.0"
  }
}
# docker-compose.yml
services:
  api:
    build: ./api
    command: npm run dev
    volumes:
      - ./api/src:/app/src  # hot reload
      - api-node-modules:/app/node_modules

Saat kamu edit file di ./api/src, nodemon restart otomatis, perubahan terlihat.

Python dengan watchdog atau uvicorn reload:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
services:
  api:
    build: ./api
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
    volumes:
      - ./api:/app

Go dengan Air atau fresh:

FROM golang:1.22-alpine
WORKDIR /app
RUN go install github.com/cosmtrek/air@latest
COPY go.mod go.sum ./
RUN go mod download
COPY . .
CMD ["air"]

Vue / Vite:

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

Vite sudah punya HMR (Hot Module Replacement) built-in. Bind mount source code cukup.

Next.js / Nuxt:

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

Next.js Fast Refresh otomatis reload saat ada perubahan.

Prinsip hot reload: bind mount source code, exclude dependencies dari bind mount (pakai anonymous/named volume), dan pakai tool watcher di dalam container (nodemon, air, uvicorn –reload).

Seed Data #

Untuk development, biasanya perlu seed data agar aplikasimu punya data untuk dikerjakan.

Init container pattern:

services:
  app:
    depends_on:
      seed:
        condition: service_completed_successfully
  
  seed:
    image: myapp:latest
    volumes:
      - ./seeds:/seeds:ro
    command: python /seeds/load.py
    restart: "no"

Seed container jalan sekali, load data, exit. App start setelahnya.

Alternative: SQL script di init:

services:
  db:
    image: postgres:16-alpine
    volumes:
      - ./db/init:/docker-entrypoint-initdb.d:ro

/docker-entrypoint-initdb.d adalah direktori khusus Postgres image. Script .sql di sini otomatis dijalankan saat database pertama kali diinisialisasi. Untuk volume yang sudah ada, kamu perlu reset volume untuk trigger init ulang.

Fixtures di dalam aplikasi:

Untuk beberapa stack, lebih simpel taruh seed di kode aplikasi dan trigger via endpoint atau CLI.

Database Tools #

Untuk development database, ada tool tambahan yang berguna.

Adminer / pgAdmin:

services:
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    depends_on:
      - db

Akses UI di http://localhost:8080 untuk query database via web. Lebih ringan dari pgAdmin.

RedisInsight:

services:
  redis-insight:
    image: redislabs/redisinsight
    ports:
      - "8001:8001"

MailHog (email testing):

services:
  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"  # SMTP
      - "8025:8025"  # Web UI

Akses http://localhost:8025 untuk lihat email yang “dikirim” oleh aplikasi kamu.

File Watching #

Secara default, Docker sync antara host dan container di bind mount menggunakan Virtual File System (varies by OS). Ada beberapa quirk:

Mac dengan Docker Desktop:

  • File events di host tidak selalu di-propagate ke container. Beberapa tool (nodemon, webpack) tidak detect perubahan.
  • Workaround: pakai docker run --polling atau file polling di tool.
  • Docker Desktop 4.x+ sudah lebih baik tapi masih bisa ada lag.

Linux:

  • File events biasanya di-propagate langsung. Tidak perlu polling.

Windows (WSL2 atau Hyper-V):

  • Performa filesystem bisa lebih lambat. Pertimbangkan file cache settings.

Untuk mengatasi file watching issue, beberapa tool punya opsi polling:

// nodemon
{
  "watch": ["src"],
  "ext": "js,json",
  "legacyWatch: true"  // pakai polling
}

Debugging #

Saat ada masalah dengan local stack, beberapa trik untuk debug.

Akses container langsung:

# Shell ke container yang sedang jalan
docker compose exec api sh

# Jalankan one-off command
docker compose run --rm api python manage.py shell

Lihat log real-time:

# Semua service
docker compose logs -f

# Service tertentu
docker compose logs -f api

# Dengan timestamp
docker compose logs -f -t api

Inspect state:

# Status semua service
docker compose ps

# Detail container
docker inspect myapp-api-1

# Resource usage
docker stats

Restart service:

# Restart satu service
docker compose restart api

# Rebuild image
docker compose build api

# Force recreate
docker compose up -d --force-recreate api

Reset state:

# Hapus semua container, network (data di volume tetap)
docker compose down

# Hapus semuanya termasuk volume
docker compose down --volumes

Network debugging dari dalam container:

docker compose exec api sh
apk add curl  # atau apt-get
curl http://db:5432  # test connection
nslookup db  # test DNS

Multi-Repo Setup #

Untuk monorepo atau multi-repo, Compose bisa manage semuanya.

Monorepo:

services:
  api:
    build: ./services/api
  web:
    build: ./services/web
  worker:
    build: ./services/worker
  db:
    image: postgres:16-alpine
  cache:
    image: redis:7-alpine

Semua service di satu Compose file, source code di sub-folder yang sama.

Multi-repo:

Untuk repositori yang berbeda, gunakan multiple Compose file.

# Repo 1: backend
cd ~/work/backend
docker compose up

# Repo 2: frontend
cd ~/work/frontend
docker compose up

Atau satukan dengan external network:

# Buat network bersama
docker network create shared

# Backend pakai network ini
# Frontend juga pakai network ini
# Mereka bisa saling resolve via hostname

Tools Tambahan untuk Development #

Tilt / Skaffold (untuk Kubernetes-style development):

Jika kamu deploy ke Kubernetes, pertimbangkan tool yang sync workflow Kubernetes dengan local development.

Devbox (per-project package manager):

Untuk mengelola bahasa dan tool versi per project, tanpa Docker.

Act (jalankan GitHub Actions locally):

Test CI workflow sebelum push.

Best Practice #

Gunakan Override File untuk Development #

Pisahkan base config (untuk semua env) dari development-specific (hot reload, debug port, dsb).

Tambah Tool Development, Bukan Kurangi Production #

Compose untuk development harus punya lebih banyak tool (debugger, admin UI) dibanding production. Bukan sebaliknya.

Selalu Bind Mount Source Code #

Untuk aplikasi yang sedang dikembangkan, source code harus di-bind mount agar hot reload jalan.

Pakai Image Tag Stabil #

# BAGUS
image: postgres:16-alpine

# BURUK
image: postgres:latest

latest bisa berubah tanpa kamu sadari, menyebabkan debug yang membingungkan.

Volume untuk Dependencies #

volumes:
  - api-node-modules:/app/node_modules
  - web-node-modules:/app/node_modules

Jangan bind mount node_modules — ia besar, platform-specific, dan akan di-overwrite oleh container.

Dokumentasikan di README #

File README.md harus punya quickstart:

## Local Development

1. Install Docker dan Docker Compose
2. Clone repo: `git clone ...`
3. Copy `.env.example` ke `.env` dan edit jika perlu
4. Run: `docker compose up`
5. Akses:
   - Web: http://localhost:5173
   - API: http://localhost:3000
   - Adminer: http://localhost:8080
6. Stop: `docker compose down`

Developer baru bisa productive dalam hitungan menit.

Contoh Lengkap: Setup Local Development untuk SaaS #

Misalkan kamu punya aplikasi SaaS dengan frontend, API, worker, database, cache, dan search engine.

services:
  frontend:
    build:
      context: ./frontend
      target: development
    command: npm run dev
    volumes:
      - ./frontend/src:/app/src
      - frontend-deps:/app/node_modules
    ports:
      - "3000:3000"
    environment:
      - VITE_API_URL=http://localhost:8080
    depends_on:
      - api

  api:
    build:
      context: ./api
      target: development
    command: uvicorn main:app --reload --host 0.0.0.0 --port 8080
    volumes:
      - ./api:/app
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://app:dev@db:5432/myapp
      - REDIS_URL=redis://cache:6379
      - ELASTICSEARCH_URL=http://search:9200
      - JWT_SECRET=devsecret
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
      search:
        condition: service_healthy

  worker:
    build: ./worker
    command: python -m worker
    environment:
      - DATABASE_URL=postgres://app:dev@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=dev
      - POSTGRES_DB=myapp
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./db/init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - "5432:5432"

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

  search:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200 || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    ports:
      - "9200:9200"

  adminer:
    image: adminer
    ports:
      - "8081:8080"
    depends_on:
      - db

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"

volumes:
  db-data:
  frontend-deps:

Stack ini punya 8 service — frontend, API, worker, database, cache, search, plus adminer dan mailhog untuk development. Tiap service punya hot reload atau konfigurasi development-appropriate.

Cara pakai:

# Setup awal
cp .env.example .env
docker compose up -d

# Akses:
# - Frontend: http://localhost:3000
# - API: http://localhost:8080/docs
# - Adminer: http://localhost:8081
# - MailHog UI: http://localhost:8025
# - Elasticsearch: http://localhost:9200

# Lihat log
docker compose logs -f

# Stop
docker compose down

Tips Performa #

Limit Resource Saat Development #

Walau di local, set limit agar tidak bikin mesin lambat.

services:
  api:
    deploy:
      resources:
        limits:
          memory: 1G

Pakai Image yang Lebih Kecil #

Untuk development, hindari image besar kalau tidak perlu. Mis. postgres:16-alpine bukan postgres:16.

Pre-pull Image #

# Download image di awal, sebelum mulai development
docker compose pull

Hemat waktu saat up pertama.

Cache Layer #

Buat Dockerfile yang optimal:

# dependencies dulu (jarang berubah)
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci

# source code (sering berubah)
COPY . .

Layer npm ci di-cache, perubahan source code tidak invalidate cache.

Secret Management untuk Development #

Untuk development, ada beberapa pattern secret management.

Plain .env file (tim kecil):

DATABASE_PASSWORD=devsecret
JWT_SECRET=devjwt

File di-.gitignore. Setiap developer punya file sendiri.

Docker secrets (lebih aman):

services:
  api:
    secrets:
      - db_password
    environment:
      - DATABASE_PASSWORD_FILE=/run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

File ./secrets/ di-.gitignore. Setiap developer isi manual atau generate otomatis.

Secret manager (untuk tim besar):

  • 1Password CLI
  • Bitwarden CLI
  • Doppler
  • Infisical

Tool ini bisa inject secret ke environment sebelum docker compose up.

Integrasi dengan IDE #

VS Code #

Dev Containers extension:

Buka repo di VS Code, klik “Reopen in Container”, VS Code otomatis attach ke container development.

// .devcontainer/devcontainer.json
{
  "name": "MyApp Dev",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "api",
  "workspaceFolder": "/app",
  "extensions": [
    "ms-python.python"
  ]
}

Docker extension:

Lihat container, image, volume, network dari VS Code. Restart, view log, exec shell.

JetBrains IDEs (GoLand, PyCharm, IntelliJ) #

Docker plugin built-in:

  • Run/Debug configuration bisa target container.
  • File watcher untuk hot reload.
  • Database tool untuk query langsung ke container database.

Recap Cheatsheet #

Pattern Tujuan
docker-compose.override.yml Default override untuk development
command: <dev-mode> Hot reload watcher
Bind mount source Hot reload source code
Anonymous volume deps Persist node_modules di container
Init container Seed data, schema migration
Adminer / pgAdmin Database UI
MailHog Email testing
File watching tweaks Mac/Windows polling
Dev Containers IDE integration
Override for production -f docker-compose.yml -f docker-compose.prod.yml

Ringkasan #

  • Local development adalah use case Docker Compose paling umum dan paling valuable. Setup stack lengkap dalam hitungan menit.
  • Override file memungkinkan konfigurasi base yang sama untuk dev/staging/production. Compose otomatis load docker-compose.override.yml jika ada.
  • Hot reload dengan bind mount source code + tool watcher di container (nodemon, air, uvicorn –reload, Vite HMR).
  • Volume untuk dependencies (node_modules, vendor/, __pycache__) — jangan bind mount ke host.
  • Seed data via init container, SQL script di /docker-entrypoint-initdb.d, atau fixture di aplikasi.
  • Database tools (Adminer, pgAdmin, RedisInsight) untuk debugging via UI.
  • MailHog untuk testing email tanpa kirim ke real recipients.
  • File watching quirks — Mac dengan Docker Desktop kadang perlu polling. Linux biasanya langsung propagate.
  • Debugging dengan docker compose exec, logs -f, inspect, restart, down --volumes untuk reset.
  • Best practice: override file untuk dev, hot reload, dependencies di volume, dokumentasi di README, image tag stabil, lebih banyak tool untuk dev bukan lebih sedikit.
  • Multi-repo Compose: pakai multiple file atau external network.
  • For production-grade dev experience, pertimbangkan Tilt/Skaffold untuk Kubernetes-like workflow.

← Sebelumnya: Healthcheck   Berikutnya: Best Practice →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact