Pertama kali coba deploy aplikasi web yang butuh database, Redis, dan Nginx sekaligus, saya langsung pusing. Jalankan satu per satu? Lupa port, lupa network, belum lagi kalau restart server. Semua harus diulang dari awal. Setelah kenal Docker Compose, semua jadi satu file yaml dan tinggal docker compose up.
Docker Compose itu tool yang memungkinkan kamu mendefinisikan dan menjalankan multiple container sekaligus. Bayangkan punya satu file yaml yang berisi semua service: aplikasi PHP/Node/Python, database MySQL/PostgreSQL, Redis untuk caching, dan Nginx sebagai reverse proxy. Semua bisa dijalankan dengan satu command.
Dulu waktu pertama kali belajar Docker, saya bikin container satu per satu pakai docker run. Hasilnya? Terminal penuh command panjang yang susah diingat. Belum lagi masalah networking antar container yang sering bikin error "connection refused".
Docker Compose menyelesaikan masalah itu. Kamu tulis semua konfigurasi di satu file docker-compose.yml, lalu jalankan semuanya sekaligus. Mau tambah service? Tinggal tambah block baru. Mau hapus? Comment out aja. Simple banget.
docker compose down dan semua bersihSebelum mulai, pahami dulu struktur folder yang biasa dipakai. Saya pakai pola ini di hampir semua project:
my-project/
docker-compose.yml
.env
app/
Dockerfile
src/
nginx/
default.conf
db/
init.sql
redis/
redis.conf
Folder app/ berisi source code aplikasi. Folder nginx/ untuk konfigurasi web server. Folder db/ punya file SQL yang jalan otomatis saat container pertama kali dibuat. Folder redis/ opsional, kalau butuh custom config.
Ini contoh nyata yang saya pakai untuk project PHP + MySQL + Redis + Nginx:
version: '3.8'
services:
app:
build:
context: ./app
dockerfile: Dockerfile
container_name: my-app
restart: unless-stopped
volumes:
- ./app/src:/var/www/html
networks:
- app-network
depends_on:
- db
- redis
nginx:
image: nginx:alpine
container_name: my-nginx
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./app/src:/var/www/html
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
networks:
- app-network
depends_on:
- app
db:
image: mysql:8.0
container_name: my-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/mysql
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3306:3306"
networks:
- app-network
redis:
image: redis:7-alpine
container_name: my-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
volumes:
db-data:
redis-data:
networks:
app-network:
driver: bridge
Perhatikan beberapa hal penting di atas. Setiap service punya restart: unless-stopped supaya otomatis nyala lagi kalau server restart. Volume dipakai untuk persist data database dan Redis. Network app-network menghubungkan semua service supaya bisa komunikasi pakai nama container.
Untuk menjalankan aplikasi PHP, kita butuh Dockerfile. Ini contoh yang saya pakai untuk CodeIgniter 4:
FROM php:8.2-fpm
# Install dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
zip \
unzip \
libzip-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd pdo pdo_mysql zip mysqli \
&& pecl install redis \
&& docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Set permissions
RUN chown -R www-data:www-data /var/www/html
EXPOSE 9000
CMD ["php-fpm"]
Dockerfile ini install semua extension PHP yang dibutuhkan: PDO untuk database, GD untuk image processing, Redis untuk caching, dan Zip untuk Composer. Base image pakai php:8.2-fpm karena Nginx akan handle web serving.
Nginx perlu dikonfigurasi supaya bisa bicara ke PHP-FPM container. Simpan file ini di nginx/default.conf:
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Yang perlu diperhatikan: fastcgi_pass app:9000 merujuk ke container "app" di port 9000. Docker Compose otomatis resolve nama container ke IP internal. Jadi kamu tidak perlu tahu IP address container, cukup pakai nama service.
Jangan taruh password database langsung di yaml! Pakai file .env:
# .env
DB_ROOT_PASSWORD=rahasia123
DB_NAME=myapp_db
DB_USER=myapp_user
DB_password=myapp_pass
Docker Compose otomatis baca file .env di folder yang sama. Di yaml, pakai syntax ${VARIABLE_NAME}. Tambahkan .env ke .gitignore supaya password tidak ikut commit ke repo publik.
Ini command yang paling sering saya pakai sehari-hari:
# Jalankan semua service di background
docker compose up -d
# Lihat log semua service (real-time)
docker compose logs -f
# Lihat log satu service aja
docker compose logs -f app
# Masuk ke container
docker compose exec app bash
# Jalankan command di dalam container
docker compose exec app php artisan migrate
# Restart satu service
docker compose restart nginx
# Stop semua service
docker compose down
# Stop dan hapus volume (hati-hati, data database hilang!)
docker compose down -v
# Rebuild image kalau Dockerfile berubah
docker compose up -d --build
# Lihat status container
docker compose ps
Tips dari saya: kalau kamu ubah Dockerfile, selalu jalankan docker compose up -d --build supaya image di-rebuild. Kalau cuma ubah file source code yang sudah di-mount via volume, container otomatis lihat perubahannya.
Salah satu kesalahan pemula yang saya alami: data database hilang setelah docker compose down. Kenapa? Karena container itu ephemeral, kalau dihapus datanya ikut hilang.
Solusinya pakai named volumes. Di yaml, definisikan di bagian volumes:
volumes:
db-data:
driver: local
redis-data:
driver: local
Volume ini persist di host machine. Kamu bisa docker compose down dan docker compose up berkali-kali, data tetap aman. Hanya docker compose down -v yang hapus volume.
Mau backup database dari volume? Pakai command ini:
# Backup database ke file SQL
docker compose exec db mysqldump -u root -p myapp_db > backup.sql
# Restore database dari file SQL
docker compose exec -T db mysql -u root -p myapp_db < backup.sql
Docker Compose bikin network otomatis untuk setiap project. Semua service di satu compose file bisa saling ping pakai nama service. Misal dari container "app", kamu bisa connect ke database pakai host "db" dan port 3306.
Di PHP, koneksi database jadi seperti ini:
<?php
// Koneksi ke MySQL container
$host = 'db'; // nama service, bukan localhost!
$port = 3306;
$dbname = 'myapp_db';
$user = 'myapp_user';
$password = 'myapp_pass';
$pdo = new PDO(
"mysql:host={$host};port={$port};dbname={$dbname}",
$user,
$password
);
Untuk Redis:
<?php
// Koneksi ke Redis container
$redis = new Redis();
$redis->connect('redis', 6379); // nama service!
$redis->set('key', 'value');
echo $redis->get('key');
Kalau kamu pakai "localhost" di dalam container, artinya container itu sendiri, bukan container lain. Selalu pakai nama service sebagai host.
Masalah klasik: aplikasi mulai sebelum database siap. Hasilnya error "connection refused". Docker Compose punya fitur healthcheck untuk ini:
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
app:
build: ./app
depends_on:
db:
condition: service_healthy
Dengan condition: service_healthy, container "app" baru jalan setelah MySQL benar-benar siap. start_period: 30s kasih waktu MySQL untuk inisialisasi sebelum health check mulai menghitung kegagalan.
Untuk production, kamu bisa kurangi ukuran image dengan multi-stage build. Contoh untuk aplikasi Node.js:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Stage pertama install semua dependency dan build. Stage kedua hanya copy hasil build dan dependency production. Hasilnya image jauh lebih kecil karena tidak ada devDependencies, source code asli, dan tool build.
Container tiba-tiba exit? Jalankan ini untuk cari tahu:
# Lihat container yang sudah mati
docker compose ps -a
# Baca log container yang crash
docker compose logs app
# Masuk ke container yang sedang running untuk debug
docker compose exec app sh
# Lihat resource usage
docker compose top
# Inspect network
docker network ls
docker network inspect my-project_app-network
Kalau container langsung exit setelah start, biasanya masalah di entrypoint command atau missing dependency. Cek log untuk tahu errornya.
Saya biasa punya tiga file compose:
docker-compose.yml # Base config
docker-compose.override.yml # Development (otomatis ter-load)
docker-compose.prod.yml # Production
Untuk production, jalankan:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
File docker-compose.override.yml otomatis ter-load kalau ada. Di situ saya taruh konfigurasi development seperti volume mount untuk hot-reload dan port exposure untuk debugging.
Docker Compose bikin hidup developer jauh lebih mudah. Satu file yaml, satu command, semua service jalan. Tidak perlu lagi install MySQL, Redis, dan Nginx di local machine. Tinggal clone repo, docker compose up -d, dan langsung ngoding.
Kalau kamu belum pernah coba Docker Compose, mulai dari yang sederhana dulu: satu aplikasi dan satu database. Setelah nyaman, tambah service lain seperti Redis, queue worker, atau monitoring. Yang penting mulai dari sekarang, karena Docker Compose sudah jadi standar industri untuk development dan deployment.