Backend••9 min read•14 views

Cara Menggunakan Docker Rollout untuk Zero Downtime Deployment

Masih sering menghadapi error 502 Bad Gateway saat deploy aplikasi di VPS? Saatnya membawa konsep rolling update ala Kubernetes ke dalam kesederhanaan Docker Compose. Pelajari arsitektur zero downtime, studi kasus integrasi, dan cara troubleshooting-nya di sini.

Bagi seorang Software Engineer atau DevOps, momen saat menjalankan perintah docker-compose up -d di production seringkali diiringi dengan kecemasan. Secara default, Docker Compose akan mematikan (stop) container lama sebelum membuat dan menyalakan container yang baru. Celah waktu beberapa detik ini—mulai dari proses stop, start, hingga aplikasi benar-benar siap menerima traffic—akan menghasilkan downtime. Pengguna Anda akan disambut oleh error 502 Bad Gateway dari Nginx.

Untuk skala enterprise, solusi standar dari masalah ini adalah menggunakan orkestrasi seperti Kubernetes (K8s) atau Docker Swarm. Namun, membawa kompleksitas K8s ke dalam environment VPS tunggal (single-node server) seringkali merupakan sebuah langkah overkill yang justru menambah beban pemeliharaan infrastruktur.

Di sinilah Docker Rollout hadir sebagai solusi game-changer. Docker Rollout adalah sebuah plugin Docker CLI yang membawa konsep rolling update ala Kubernetes ke dalam ekosistem Docker Compose yang sederhana.

Artikel ini akan mengupas tuntas cara menggunakan Docker Rollout, lengkap dengan studi kasus deployment API dan playbook "Cara Solve" ala Senior Engineer untuk mengatasi berbagai skenario edge-case di production.

Membedah Konsep: Bagaimana Docker Rollout Bekerja?

Sebelum masuk ke implementasi, seorang engineer harus memahami apa yang terjadi di balik layar. Docker Rollout tidak memodifikasi core daemon Docker, melainkan melakukan orkestrasi perintah secara cerdas.

Berikut adalah komparasi proses deployment:

Fase Default docker compose up -d Menggunakan docker rollout
Fase 1 Mengirim sinyal SIGTERM ke container lama. Membuat container baru (Replika ke-2).
Fase 2 Container lama mati (Downtime dimulai). Menunggu status Healthcheck container baru menjadi Healthy.
Fase 3 Membuat dan menjalankan container baru. Reverse proxy (otomatis) mengalihkan traffic ke container baru.
Fase 4 Container baru booting (Downtime masih terjadi). Mengirim sinyal SIGTERM ke container lama untuk proses shutdown.
Fase 5 Container baru siap menerima traffic. Container lama dihapus. Zero Downtime tercapai.

Syarat mutlak agar skema di atas berjalan adalah: Anda tidak boleh melakukan binding port secara statis ke host (misal: ports: - "8080:8080") dan Anda wajib menggunakan Reverse Proxy yang mendeteksi perubahan IP container secara dinamis (seperti Traefik atau nginx-proxy).

Tahap 1: Instalasi Docker Rollout Plugin

Docker Rollout diinstal sebagai plugin CLI dari Docker, bukan sebagai standalone binary. Pastikan Anda sudah menggunakan Docker Compose V2 (perintah docker compose, bukan docker-compose versi lama).

Jalankan perintah berikut di server production Anda:

Bash
# 1. Buat direktori untuk plugin Docker CLI jika belum ada
mkdir -p ~/.docker/cli-plugins

# 2. Download script docker-rollout versi terbaru dari GitHub
curl https://raw.githubusercontent.com/wowu/docker-rollout/main/docker-rollout -o ~/.docker/cli-plugins/docker-rollout

# 3. Berikan hak akses eksekusi (executable) pada script tersebut
chmod +x ~/.docker/cli-plugins/docker-rollout
Untuk memverifikasi instalasi, jalankan:

Bash
docker rollout --help
Jika berhasil, Anda akan melihat panduan opsi CLI dari Docker Rollout.

Tahap 2: Studi Kasus - Deployment API (Golang + PostgreSQL)

Mari kita gunakan studi kasus yang relevan dengan ekosistem modern. Kita akan mendeploy sebuah Backend API untuk sistem Point of Sale (POS) yang ditulis menggunakan Golang. API ini berada di balik Nginx.

1. Persiapan Reverse Proxy (nginx-proxy)

Kita tidak akan menggunakan vanilla Nginx karena Nginx standar melakukan cache terhadap resolusi DNS. Ketika container API baru mendapat IP internal baru, vanilla Nginx akan tetap mengirim traffic ke IP lama sampai Nginx di-reload.

Solusi terbaiknya adalah menggunakan jwilder/nginx-proxy. Sistem ini akan mendengarkan event dari Docker socket dan secara otomatis menulis ulang konfigurasi Nginx ketika ada container baru yang up.

Buat file docker-compose.proxy.yml (Jalankan sekali di server):

YAML
services:
  nginx-proxy:
    image: nginxproxy/nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      - web-network

networks:
  web-network:
    external: true
Jalankan dengan: docker network create web-network && docker compose -f docker-compose.proxy.yml up -d

2. Refactor File Konfigurasi Aplikasi

Sekarang, mari kita lihat file docker-compose.yml untuk API Golang kita. Ada beberapa penyesuaian wajib dari konfigurasi standar.

YAML
# docker-compose.yml (Untuk Aplikasi)
services:
  api:
    image: my-registry.com/pos-api:latest
    # ANTI-PATTERN DOCKER ROLLOUT:
    # container_name: pos-api-1 (HAPUS INI)
    # ports: (HAPUS INI)
    #   - "8080:8080"
    expose:
      - "8080"
    environment:
      - VIRTUAL_HOST=api.notopos.id
      - VIRTUAL_PORT=8080
      - DB_HOST=postgres
    networks:
      - web-network
      - db-network
    # WAJIB ADA: Healthcheck
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 10s

  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_PASSWORD=secret
    networks:
      - db-network
    volumes:
      - pgdata:/var/lib/postgresql/data

networks:
  web-network:
    external: true
  db-network:

volumes:
  pgdata:

Analisis pada Konfigurasi di atas:

  1. Penghapusan container_name: Docker Rollout harus menjalankan 2 container sekaligus saat transisi. Jika nama di-hardcode, akan terjadi bentrok (conflict). Docker Rollout akan otomatis menamai container menjadi [project]-api-1 dan [project]-api-2.
  2. Penghapusan ports dan Penggunaan expose: Sama seperti nama, port host tidak bisa dipakai berbarengan. Kita hanya meng-expose port 8080 ke dalam internal network Docker.
  3. Environment VIRTUAL_HOST: Ini adalah magic variable yang ditangkap oleh nginx-proxy.
  4. Kehadiran healthcheck: Ini adalah "nyawa" dari zero downtime. Docker Rollout akan menahan eksekusi penghapusan container lama sampai endpoint /health ini mengembalikan status HTTP 200 secara konsisten.

3. Eksekusi Deployment dengan Docker Rollout

Saat ada versi kode baru yang sudah di-build menjadi image [my-registry.com/pos-api:latest](https://my-registry.com/pos-api:latest), proses deployment yang sebelumnya menggunakan docker compose up -d kini diganti menjadi:

Bash
# 1. Pull image terbaru dari registry
docker compose pull api

# 2. Lakukan rolling update khusus untuk service "api"
docker rollout api
Output Log yang akan Anda lihat:

Plaintext
Scaling service api to 2 instances...
Waiting for container project-api-2 to become healthy...
Container project-api-2 is healthy!
Stopping old container project-api-1...
Removing old container project-api-1...
Rollout completed successfully!
Pada titik ini, aplikasi Anda telah terupdate tanpa ada satupun request HTTP yang mendapatkan pesan error dari Nginx.

Tahap 3: "Cara Solve" - Menyelesaikan Masalah (Troubleshooting Playbook)

Dalam praktiknya di production, zero downtime tidak selalu berjalan mulus hanya bermodalkan perintah di atas. Sebagai Senior Engineer, Anda dituntut untuk memahami cara memitigasi berbagai masalah turunan. Berikut adalah playbook pemecahan masalah (Cara Solve) untuk skenario kompleks.

Masalah 1: Request Terputus Tiba-tiba di Tengah Transisi (Active Connection Drop)

Gejala:
Meskipun container baru sudah menyala, ada beberapa pengguna yang request-nya (misalnya proses upload file atau query laporan yang berat) mendadak gagal saat transisi terjadi.

Root Cause:
Docker Rollout mematikan container lama segera setelah container baru healthy. Namun, Reverse Proxy (Nginx) mungkin membutuhkan waktu 1-2 detik untuk menyadari perubahan routing, dan container lama mungkin masih memproses ratusan request yang belum selesai (in-flight requests).

Cara Solve: Implementasi Container Draining dengan Pre-stop Hook
Kita harus "menipu" sistem. Sebelum container lama dimatikan, kita harus mengubah statusnya menjadi unhealthy secara sengaja. Tujuannya agar Nginx berhenti mengirim traffic BARU ke container lama, tetapi membiarkan traffic yang SEDANG BERJALAN selesai diproses.

Tambahkan instruksi pre-stop-hook via label di docker-compose.yml:

YAML
    labels:
      docker-rollout.pre-stop-hook: "touch /tmp/drain && sleep 15"
Lalu, ubah logika endpoint /health di kode sumber aplikasi Golang Anda:

Go
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
    // Jika file drain eksis, kembalikan HTTP 503 (Unhealthy)
    if _, err := os.Stat("/tmp/drain"); err == nil {
        w.WriteHeader(http.StatusServiceUnavailable)
        return
    }
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
})
Bagaimana ini bekerja:
Saat docker rollout selesai menyiapkan container baru, ia mengeksekusi pre-stop-hook di container lama. File /tmp/drain dibuat. Healthcheck Docker kini mendeteksi container lama sebagai unhealthy. Nginx otomatis menghapus container lama dari upstream routing. Perintah sleep 15 memberikan waktu 15 detik bagi container lama untuk menyelesaikan semua in-flight request sebelum akhirnya benar-benar dihentikan (SIGTERM). Ini adalah Zero Downtime yang absolut.

Masalah 2: Database Migration Locking

Gejala:
Aplikasi versi baru menggunakan struktur tabel database (skema) yang diubah. Jika container lama dan baru berjalan secara bersamaan selama fase rollout, container lama akan crash atau menghasilkan data yang korup karena skema database telah berubah, sedangkan codebase lama belum mendukungnya.

Root Cause:
Keterikatan antara lifecycle aplikasi dan lifecycle database. Menjalankan Auto-Migrate (seperti GORM AutoMigrate atau Prisma migrate deploy) saat aplikasi booting adalah sebuah anti-pattern di arsitektur Zero Downtime.

Cara Solve: Backward-Compatible Migrations & Separation of Concerns

  1. Pisahkan eksekusi: Migrasi database tidak boleh dijalankan di dalam container aplikasi (via entrypoint). Gunakan container terpisah atau jalankan sebelum rollout.

    Bash
    docker compose run --rm api migrate up
    docker rollout api
    
  2. Aturan Backward Compatibility: Skema database Anda harus selalu mendukung 2 versi kode secara bersamaan (N dan N-1).

    • Jika Anda ingin menghapus sebuah kolom, rilis kode baru yang berhenti membaca/menulis kolom tersebut lebih dahulu (Deploy 1).
    • Setelah container lama mati, baru lakukan rilis migrasi yang men-drop kolom tersebut (Deploy 2).
    • Jangan pernah me-rename kolom secara langsung. Buat kolom baru, copy datanya, baca/tulis dari 2 kolom, lalu hapus kolom lama secara bertahap.

Masalah 3: Rollout Stuck (Timeout / Infinite Wait)

Gejala:
Terminal Anda berhenti di pesan Waiting for container project-api-2 to become healthy... selama berpuluh-puluh menit, atau gagal dengan error Timeout.

Root Cause:

  • Healthcheck URL salah atau memerlukan otentikasi.
  • Aplikasi crash saat booting (misalnya karena kehilangan Environment Variable baru), sehingga tidak pernah mencapai status Healthy.
Cara Solve: Debugging & Custom Timeout

  1. Investigasi Container Baru: Jangan hentikan proses rollout dulu. Buka terminal baru dan cek log container yang sedang booting:

    Bash
    docker logs project-api-2 -f
    
    Cari stack trace atau error yang menyebabkan API gagal berjalan.
  2. Atur Parameter start_period: Jika aplikasi Anda adalah aplikasi raksasa berbasis Java Spring Boot atau monolith PHP besar yang butuh 30-40 detik untuk inisialisasi awal, tambah nilai start_period di file docker-compose.yml Anda (misal start_period: 60s).
  3. Konfigurasi Timeout Rollout: Secara default, docker rollout menunggu 60 detik. Anda bisa memperpanjang toleransi waktunya melalui parameter -t:

    Bash
    docker rollout api -t 120
    

Masalah 4: Port Conflict Akibat Hardcoded Port di Codebase

Gejala:
Docker merespon dengan Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in use.

Root Cause:
Walaupun Anda sudah menghapus deklarasi ports di file docker-compose.yml, aplikasi di dalam container Anda secara agresif mencoba membuka koneksi yang bentrok di level network stack yang salah, atau Anda menjalankan aplikasi mode host network.

Cara Solve:
Pastikan network_mode: host tidak aktif. Docker Rollout mewajibkan isolasi jaringan (mode bridge default). Pastikan Reverse proxy bertindak sebagai mediator yang menjembatani port eksternal 80/443 dengan port internal 8080 aplikasi Anda.

Kesimpulan

Beralih dari skema Hard Stop (docker-compose up -d) menuju Zero Downtime Deployment dengan docker rollout merupakan lompatan maturitas infrastruktur yang masif. Mengadopsi teknologi ini tidak hanya meningkatkan SLA (Service Level Agreement) sistem yang Anda bangun, namun juga memberikan ketenangan batin (peace of mind) kepada tim Engineering saat harus melakukan deployment fitur baru di jam kerja yang padat.

Kunci utama kesuksesan penggunaan Docker Rollout terletak pada 3 pondasi arsitektural:

  1. Container yang Stateless: Hindari menyimpan file aset atau session di dalam file system container (gunakan S3 bucket atau Redis).
  2. Reverse Proxy Dinamis: Traefik atau Nginx-Proxy adalah pendamping wajib agar IP internal container baru bisa terdeteksi otomatis.
  3. Healthcheck yang Akurat: Jangan hanya mengandalkan pengecekan ping. Pastikan endpoint /health aplikasi Anda benar-benar mengecek kesiapan koneksi database dan cache sebelum merespon dengan status 200 OK.
Dengan menguasai tooling dan pemecahan masalah yang telah diuraikan di atas, Anda siap membawa reliabilitas deployment kelas enterprise tanpa harus dipusingkan oleh manajemen cluster yang rumit. Selamat melakukan refactoring dan nikmati deploy di hari Jumat tanpa drama!
Sigit Wasis Subekti

Sigit Wasis Subekti

Software Engineer & Tech Educator

Software Engineer and Tech Educator sharing insights on web development and software architecture.