DevOps

Docker Compose Multi-Service - Deploy App Database Redis Sekaligus

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.

Kenapa Pakai Docker Compose?

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.

  • Reproducible: Tim lain tinggal clone repo, jalankan compose, langsung jalan
  • Version control: File yaml masuk git, perubahan konfigurasi ter-track
  • Isolated: Setiap service punya container sendiri, tidak saling ganggu
  • Easy cleanup: docker compose down dan semua bersih

Struktur Project yang Umum

Sebelum 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.

Contoh docker-compose.yml Lengkap

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.

Dockerfile untuk Aplikasi PHP

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.

Konfigurasi Nginx sebagai Reverse Proxy

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.

Environment Variables dengan .env

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.

Command Docker Compose yang Wajib Dikuasai

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.

Persist Data dengan Named Volumes

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

Networking Antar Container

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.

Health Check untuk Service Dependencies

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.

Multi-Stage Build untuk Production

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.

Tips Debugging Container yang Bermasalah

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.

Override untuk Development vs Production

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.

Kesimpulan

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.


You may also like


0 Comments


Leave a Reply

Comments with links or spam keywords will be rejected.
Scroll to Top