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 --pollingatau 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.ymljika 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 --volumesuntuk 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.