Pernah nggak sih, kamu sudah berhasil bikin satu container jalan dengan Docker, tapi begitu coba hubungkan beberapa service sekaligus — database, backend, frontend, redis — rasanya ribet banget? Harus buka banyak terminal, jalankan perintah docker run satu per satu, belum lagi soal networking antar container. Capek, kan?
Nah, di situlah Docker Compose hadir sebagai penyelamat. Dengan satu file YAML dan satu perintah, kamu bisa menjalankan seluruh stack aplikasi sekaligus. Kalau kamu seorang developer yang ingin workflow development lebih rapi dan efisien, artikel ini wajib kamu baca sampai habis.
Di tutorial ini, kita akan belajar Docker Compose dari nol secara praktis. Kita akan bikin contoh aplikasi multi-container yang terdiri dari Node.js, PostgreSQL, dan Redis — sebuah stack yang sangat umum dipakai di dunia nyata.
Apa Itu Docker Compose dan Kenapa Kamu Butuh?
Sebelum masuk ke kode, penting untuk paham dulu konsep dasarnya.
Docker Compose adalah tool yang memungkinkan kamu mendefinisikan dan menjalankan beberapa container Docker sekaligus menggunakan satu file konfigurasi bernama docker-compose.yml (atau compose.yml di versi terbaru). Bayangkan dia semacam “skrip orkestra” yang mengatur semua container supaya bisa bekerja bersama.
Tanpa Docker Compose, kamu harus menjalankan perintah-perintah seperti ini satu per satu:
docker network create mynetwork
docker run -d --name postgres --network mynetwork -e POSTGRES_PASSWORD=secret postgres:16
docker run -d --name redis --network mynetwork redis:7
docker run -d --name app --network mynetwork -p 3000:3000 my-node-app
Ribet? Banget. Apalagi kalau ada dependency, environment variable, volume mounting, dan sebagainya.
Dengan Docker Compose, semua itu cukup jadi satu file dan satu perintah:
docker compose up
Selesai. Semua service langsung berjalan bersamaan, saling terhubung lewat network yang sama, dengan konfigurasi yang sudah kamu tentukan.
Kapan Waktu yang Tepat Pakai Docker Compose?
- Development lokal: Kamu butuh database, cache, message queue, dan app server berjalan bersamaan.
- Testing/CI: Butuh environment yang reproducible untuk test.
- Prototyping: Ingin cepat spin up full stack tanpa setup manual.
- Small-scale deployment: Untuk staging atau bahkan production sederhana, Compose sudah sangat memadai.
Persiapan: Apa Saja yang Perlu Diinstal?
Sebelum mulai coding, pastikan environment kamu sudah siap. Ini dia yang kamu butuhkan:
1. Docker Engine
Pastikan Docker sudah terinstal di mesin kamu. Cek dengan:
docker --version
Kalau belum terinstal, kamu bisa ikuti panduan resmi dari docs.docker.com.
2. Docker Compose
Kabar baiknya, Docker Compose V2 sekarang sudah built-in langsung ke dalam Docker CLI. Jadi kalau kamu sudah install Docker Desktop (Windows/Mac) atau Docker Engine terbaru (Linux), Compose sudah tersedia.
Cek dengan:
docker compose version
Kalau muncul versinya, kamu siap. Perhatikan bahwa perintahnya sekarang docker compose (spasi), bukan docker-compose (strip) seperti versi lama. Versi lama masih bisa dipakai tapi sudah deprecated.
3. Text Editor
VS Code, Vim, Nano, atau editor apapun yang kamu suka. Yang penting nyaman buat nulis YAML.
Struktur Project: Contoh Aplikasi Node.js + PostgreSQL + Redis
Sekarang kita bikin project nyata. Stack yang kita pakai:
- Node.js (Express) — backend API
- PostgreSQL — database utama
- Redis — caching layer
Buat folder project baru:
mkdir compose-demo && cd compose-demo
File index.js — Aplikasi Node.js
const express = require("express");
const { Pool } = require("pg");
const redis = require("redis");
const app = express();
const port = 3000;
// Koneksi PostgreSQL — pakai nama service "db" dari docker-compose
const pgPool = new Pool({
host: "db",
port: 5432,
user: "postgres",
password: "postgres",
database: "appdb",
});
// Koneksi Redis — pakai nama service "redis" dari docker-compose
const redisClient = redis.createClient({
url: "redis://redis:6379",
});
redisClient.on("error", (err) => console.error("Redis error:", err));
app.get("/", async (req, res) => {
// Cek cache dulu
const cached = await redisClient.get("hit_count");
if (cached) {
return res.json({ source: "cache", hit_count: parseInt(cached) });
}
// Kalau tidak ada di cache, query database
const result = await pgPool.query(
"SELECT hit_count FROM counters WHERE name = 'main'"
);
const hitCount = result.rows[0]?.hit_count || 0;
// Simpan ke cache selama 60 detik
await redisClient.setEx("hit_count", 60, hitCount.toString());
res.json({ source: "database", hit_count: hitCount });
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
File package.json
{
"name": "compose-demo",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.12.0",
"redis": "^4.6.10"
}
}
File Dockerfile untuk App
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
File init.sql — Inisialisasi Database
CREATE TABLE IF NOT EXISTS counters (
name VARCHAR(50) PRIMARY KEY,
hit_count INTEGER DEFAULT 0
);
INSERT INTO counters (name, hit_count) VALUES ('main', 42)
ON CONFLICT (name) DO NOTHING;
Membuat docker-compose.yml: Inti dari Semuanya
Sekorang kita masuk ke bagian paling penting. Buat file docker-compose.yml di root project:
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- .:/app
- /app/node_modules
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: appdb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
restart: unless-stopped
volumes:
pgdata:
redisdata:
Wah, kayaknya banyak ya? Tenang, kita bedah satu per satu.
Penjelasan Per Bagian
services — Ini daftar semua container yang mau kita jalankan. Setiap service punya nama unik (app, db, redis) yang sekaligus jadi hostname di internal Docker network. Makanya di kode Node.js tadi kita bisa langsung pakai host: "db" dan url: "redis://redis:6379".
build — Memberitahu Compose untuk build image dari Dockerfile di direktori yang ditentukan. Dipakai di service app karena kita punya Dockerfile sendiri.
image — Kalau kamu tidak build sendiri tapi pakai image yang sudah ada di Docker Hub, cukup tulis nama imagenya di sini. Dipakai di db dan redis.
ports — Mapping port dari container ke host. Formatnya host:container. Jadi "3000:3000" artinya port 3000 di container dipetakan ke port 3000 di localhost.
environment — Set environment variables di dalam container. Untuk PostgreSQL, ini penting banget karena image postgres butuh POSTGRES_USER, POSTGRES_PASSWORD, dan POSTGRES_DB saat pertama kali diinisialisasi.
depends_on — Mengatur urutan startup. Service app baru akan dijalankan setelah db healthy dan redis sudah started. Ini mencegah error koneksi di aplikasi karena database belum siap.
volumes — Ada dua jenis di sini:
- Named volume (
pgdata:/var/lib/postgresql/data): Data database tetap tersimpan walaupun container dihapus. Penting untuk data persisten. - Bind mount (
.:/app): Mount direktori lokal ke container. Cocok untuk development supaya perubahan kode langsung ter-reflect tanpa rebuild.
healthcheck — Cara kita mengecek apakah service benar-benar sudah siap. Untuk PostgreSQL, kita pakai pg_isready. Service baru dianggap “healthy” kalau healthcheck berhasil.
restart — Kebijakan restart otomatis. unless-stopped artinya container akan restart sendiri kalau crash, kecuali kamu yang menghentikannya secara manual.
Menjalankan dan Mengelola Multi-Container
First Run
Sekarang saatnya membuktikan semua bekerja. Di root project, jalankan:
docker compose up --build
Flag --build memaksa Compose untuk build image ulang. Penting dijalankan pertama kali atau setiap kali kamu ubah Dockerfile atau kode.
Kamu akan melihat log dari ketiga service bercampur di terminal. Compose menampilkan log dengan warna berbeda untuk setiap service supaya mudah dibedakan.
Buka terminal baru dan test:
curl http://localhost:3000
Kalau berhasil, kamu akan dapat response JSON:
{
"source": "database",
"hit_count": 42
Di request kedua dalam 60 detik, data akan diambil dari cache:
{
"source": "cache",
"hit_count": 42
}
Perintah-Perintah Penting
Berikut perintah Docker Compose yang wajib kamu hafal:
# Jalankan di background (detached mode)
docker compose up -d
# Lihat status semua container
docker compose ps
# Lihat log real-time
docker compose logs -f
# Lihat log service tertentu
docker compose logs -f app
# Hentikan semua container (data tetap aman di volume)
docker compose down
# Hentikan DAN hapus semua volume (hati-hati: data hilang!)
docker compose down -v
# Jalankan perintah di dalam container
docker compose exec db psql -U postgres -d appdb
# Rebuild satu service saja
docker compose up -d --build app
# Scale satu service jadi banyak instance
docker compose up -d --scale app=3
Networking di Docker Compose
Salah satu fitur paling powerful dari Docker Compose adalah networking otomatis. Secara default, Compose membuat network baru bernama <nama-folder>_default dan menghubungkan semua service ke network tersebut.
Artinya, setiap service bisa saling “melihat” satu sama lain menggunakan nama service sebagai hostname. Inilah kenapa di kode Node.js kita cukup tulis host: "db" — Docker DNS akan me-resolve nama itu ke IP container PostgreSQL yang benar.
Custom Network
Kalau kamu butuh isolasi lebih lanjut, kamu bisa define network sendiri:
services:
app:
build: .
networks:
- frontend
- backend
db:
image: postgres:16-alpine
networks:
- backend
redis:
image: redis:7-alpine
networks:
- backend
nginx:
image: nginx:alpine
ports:
- "80:80"
networks:
- frontend
networks:
frontend:
backend:
Dengan konfigurasi ini, nginx dan app bisa saling berkomunikasi lewat network frontend, sementara app, db, dan redis berkomunikasi lewat backend. Tapi nginx tidak bisa langsung mengakses db atau redis. Ini bagus untuk keamanan!
Tips Lanjutan: Environment Variables dan Override
Menggunakan .env File
Daripada hardcode password di docker-compose.yml, lebih baik pakai file .env:
# .env
POSTGRES_USER=postgres
POSTGRES_PASSWORD=mysecretpassword
POSTGRES_DB=appdb
APP_PORT=3000
Lalu referensikan di docker-compose.yml:
services:
app:
build: .
ports:
- "${APP_PORT}:3000"
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
Jangan lupa tambahkan .env ke .gitintore supaya password tidak ter-commit ke repository:
.env
node_modules
Override untuk Berbagai Environment
Kamu bisa bikin file override untuk membedakan konfigurasi antara development dan production:
docker-compose.override.yml (otomatis ter-load):
services:
app:
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
docker-compose.prod.yml (di-load manual):
services:
app:
volumes: []
environment:
- NODE_ENV=production
deploy:
replicas: 3
resources:
limits:
cpus: "0.5"
memory: 512M
Jalankan untuk production:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Best Practices yang Perlu Kamu Ingat
Setelah cukup lama pakai Docker Compose, berikut beberapa best practices yang menurut saya wajib diikuti:
1. Selalu pakai depends_on dengan healthcheck. Jangan cuma andalkan depends_on biasa tanpa condition. Container bisa “started” tapi belum benar-benar ready. Healthcheck memastikan service benar-benar siap menerima koneksi.
2. Pakai named volume untuk data persisten. Jangan simpan data penting (database, file upload) di dalam container. Container bersifat ephemeral — bisa dihapus kapan saja. Named volume memastikan data tetap aman.
3. Pisahkan konfigurasi berdasarkan environment. Jangan pakai satu docker-compose.yml untuk semua kebutuhan. Gunakan file override supaya konfigurasi development dan production tidak saling bentrok.
4. Jangan hardcode secrets. Pakai .env file atau Docker secrets untuk password, API key, dan konfigurasi sensitif lainnya.
5. Gunakan image versi spesifik. Hindari postgres:latest — pakai postgres:16-alpine supaya build lebih reproducible dan kamu tahu persis versi yang dipakai.
6. Manfaatkan restart policy. Set restart: unless-stopped untuk service penting supaya otomatis recover kalau crash.
7. Perhatikan .dockerignore. Buat file .dockerignore supaya build context tidak mengirim file yang tidak perlu:
Masih Bingung? Ini FAQ yang Sering Ditanyakan
Q: Bedanya docker-compose.yml dan compose.yml yang mana yang benar?
Keduanya benar. Di Docker Compose V2, nama file default yang direkomendasikan adalah compose.yml (tanpa “docker-” di depan). Tapi docker-compose.yml masih didukung penuh dan bisa dipakai tanpa masalah. Kalau kamu bikin project baru, pakai compose.yml saja lebih ringkas.
Q: Bisa nggak pakai Docker Compose untuk production?
Bisa, tapi ada batasannya. Docker Compose sangat cocok untuk development, testing, dan deployment skala kecil. Untuk production skala besar dengan kebutuhan auto-scaling, rolling update, dan high availability, sebaiknya pertimbangkan Docker Swarm atau Kubernetes. Docker Compose tidak dirancang untuk orchestration di level tersebut.
Q: Kenapa container app saya langsung crash saat pertama kali dijalankan?
Penyebab paling umum adalah app mencoba koneksi ke database sebelum database siap. Solusinya: gunakan depends_on dengan condition: service_healthy dan tambahkan healthcheck di service database. Alternatif lain, implementasikan retry logic di kode aplikasi kamu. Banyak library database yang sudah mendukung auto-retry.
Q: Bagaimana cara melihat isi database PostgreSQL yang jalan di container?
Jalankan perintah ini:
docker compose exec db psql -U postgres -d appdb
Ini akan membuka shell psql langsung ke dalam container database. Kamu bisa jalankan SQL query seperti biasa. Ketik \q untuk keluar.
Q: Apakah volume akan terhapus saat docker compose down?
Tidak. Secara default, docker compose down hanya menghapus container dan network, tapi volume tetap tersimpan. Untuk menghapus volume juga, kamu harus menambahkan flag -v: docker compose down -v. Hati-hati, ini akan menghapus semua data di database!
Mulai Perjalanan Docker Compose Kamu!
Docker Compose benar-benar mengubah cara kita membangun dan menjalankan aplikasi multi-container. Yang dulunya butuh puluhan perintah dan banyak terminal, sekarang cukup satu file YAML dan satu perintah. Workflow development jadi jauh lebih rapi, cepat, dan yang paling penting — reproducible. Tim baru tinggal clone repo, jalankan docker compose up, dan langsung punya environment yang sama persis.
Tutorial ini baru permukaan dari apa yang bisa Docker Compose lakukan. Masih banyak fitur lain yang belum kita bahas — multi-stage build, Docker secrets, resource limits, dan masih banyak lagi. Tapi fondasi yang kamu pelajari di sini sudah cukup untuk membangun sebagian besar aplikasi modern.
Kalau kamu punya pertanyaan lebih lanjut, butuh bantuan setup Docker Compose untuk project-mu, atau ingin diskusi tentang arsitektur containerization yang tepat untuk kebutuhanmu, jangan ragu untuk menghubungi saya di [email protected]. Saya dengan senang hati akan membantu!
Pertanyaan Lain?
Kalau ada pertanyaan yang belum terjawab di artikel ini, cek bagian FAQ di atas atau langsung kirim email ke [email protected]. Saya berusaha membalas setiap email dalam 1×24 jam.
Selamat ngoding, dan semoga Docker Compose bikin hidupmu lebih mudah! 🐳