Yuke Brilliant
  • HOME
  • PROJECTS
  • BLOG
  • CONTACT
Let's Talk
  1. Home
  2. /
  3. Blog
  4. /
  5. Zero-Downtime Deployment Dengan Docker Rollout
DevOps
IDEN

Zero-Downtime Deployment Dengan Docker Rollout

Cerita deploy web pendaftaran lomba kampus: solo dev, budget 2jt, peserta panik kena 502 pas deadline mepet. Ini cara zero-downtime deployment di Docker Compose dengan docker-rollout tanpa ribet pasang Kubernetes wkwkwk πŸš€

Yuke Brilliant HestiavinΒ·Sep 21, 2026Β·12 min read
Share:
Cover image for Zero-Downtime Deployment Dengan Docker Rollout

On this page

  1. 1.Kenapa docker compose up -d bikin downtime
  2. 2.Berbagai cara deploy tanpa downtime
  3. 3.Blue-green manual
  4. 4.Orchestrator (Swarm / Kubernetes)
  5. 5.Deploy tool (Kamal, Dokku, dsb)
  6. 6.docker-rollout
  7. 7.Kenalan dengan docker-rollout
  8. 8.Persyaratan
  9. 9.Instalasi docker-rollout
  10. 10.Setup
  11. 11.1. Pasang reverse proxy
  12. 12.2. Siapkan compose.yml service
  13. 13.3. Tambahkan healthcheck
  14. 14.4. Rollout pertama
  15. 15.Draining: jangan buang request yang sedang jalan
  16. 16.Script deploy lengkap
  17. 17.Catatan dan batasan
  18. 18.Penutup
  19. 19.Referensi

Beberapa waktu lalu saya sempat dapat project pembuatan website pendaftaran dan pengumpulan berkas lomba untuk salah satu kegiatan di departemen kampus saya di ITS. Statusnya solo developer, pengerjaan serba mepet, mana bayarannya kecil wkwkwk cuma 2jt... 😭 Jadi arsitekturnya dibuat se-sat-set dan seringkas mungkin: satu VPS Linux murah, satu file docker-compose.yml, dan aplikasi dibungkus rapi ke dalam Docker.

Alur deploy yang saya pakai adalah alur klasik sejuta umat: push kode dari laptop, SSH ke VPS, jalankan git pull, build image baru dengan docker compose build, lalu hajar docker compose up -d. Sederhana, cepat, dan selama masa persiapan rasanya aman-aman saja, sampai akhirnya masuk ke hari-hari terakhir pendaftaran...

Kamu yang pernah mengurus website event atau lomba pasti paham betul kelakuan peserta: 90% baru mendaftar di jam-jam terakhir menjelang batas akhir pendaftaran mepet wkwkwk πŸ˜… Mana form pendaftaran lomba itu inputnya banyak sekali. Bukan cuma nama dan email, tapi ada belasan field: nama tim, data ketua, data anggota, asal instansi, plus rentetan unggahan berkas seperti scan Kartu Tanda Mahasiswa (KTM), bukti follow akun sponsor, bukti share poster ke IG story, sampai slip bukti transfer pembayaran.

Nah, pas trafik lagi ramai-ramainya dan peserta lagi khusyuk mengunggah berkas, ada bug validasi kecil yang dilaporkan panitia dan harus segera saya perbaiki. Tanpa pikir panjang, saya push perbaikannya ke server lalu jalankan docker compose up -d. Tepat di detik itu juga: DUARRR! Layar peserta langsung memunculkan pesan 502 Bad Gateway πŸ’€ Request upload berkas yang sedang berjalan 80% langsung gagal seketika, form tereset ke awal, peserta panik dan mengamuk di helpdesk panitia, dan ujung-ujungnya developer yang kena todong wkwkwk.

This is fine
Suasana peserta pas lagi submit berkas lomba menit terakhir terus tiba-tiba kena 502 Bad Gateway 😭

Kalau di project hobi pribadi yang pengunjungnya cuma kita sendiri, jeda mati 5-10 detik mungkin bukan masalah besar. Tapi begitu sistemnya dipakai oleh pengguna riil, menerima request webhook, atau menangani bot dan trafik pendaftaran yang masuk terus-menerus di detik-detik kritis, downtime beberapa detik saja langsung jadi bencana operasional.

Sebenarnya saya tahu solusinya: ya pasang Kubernetes. Ada fitur rolling update bawaan yang bikin peralihan container berjalan mulus tanpa kedip. Tapi mari realistis: masa untuk side project kampus dan dikerjakan sendirian harus setup Kubernetes? Ga worth it banget jir, mana bayarannya kecil wkwkwk cuma 2jt! Yang ada RAM VPS 20 ribu perak habis cuma buat menjalankan control plane K8s, belum lagi pusing mengurus file YAML yang panjang bgt 🀣

Chaos pizza fire
Gambaran mental developer kalau nekat pasang Kubernetes di VPS murah cuma demi rolling update wkwkwk

Dari situlah muncul pertanyaan benang merahnya: "Masa harus pasang Kubernetes cuma buat menghilangkan jeda downtime 5 detik?"

Setelah mencari jalan tengah yang tidak membebani server dan pikiran, akhirnya saya menemukan solusinya: docker-rollout. Cuma modal satu plugin Docker CLI kecil, kita bisa menikmati zero-downtime deployment di Docker Compose tanpa perlu pasang Docker Swarm apalagi Kubernetes, lengkap dengan fitur draining agar request yang sedang berjalan tidak putus di tengah jalan! πŸ”₯

Kenapa docker compose up -d bikin downtime

Banyak developer mengira opsi -d (detached mode) pada Docker Compose otomatis membuat pergantian container berjalan mulus di latar belakang. Anggapan ini keliru wkwkwk! Perintah docker compose up -d pada service yang sudah berjalan melakukan mekanisme recreate (bikin ulang), bukan rolling replace.

Urutan kejadian aslinya di balik layar adalah seperti ini:

Anatomi Downtime docker compose up -d
Anatomi downtime pada perintah docker compose up -d (t1 sampai t4 memicu 502)

Downtime yang dialami pengguna adalah akumulasi dari waktu mematikan container lama ditambah proses booting aplikasi baru. Di bahasa seperti Go mungkin cepat, hanya 1 sampai 2 detik. Tetapi di framework seperti Next.js, Node.js (Express/Nest), atau Laravel, proses runtime init, inisialisasi connection pool database, dan parsing rute bisa memakan waktu 5 hingga 20 detik! Selama rentang waktu itulah port aplikasi tertutup, dan reverse proxy di depannya akan langsung melempar pesan 502 Bad Gateway atau Connection Refused ke browser pengguna.

Kondisi ini memakan dua jenis korban:

  • Incoming request: Pengguna baru yang baru membuka halaman atau mengirim data saat container sedang mati akan langsung disambut error 502.
  • In-flight request: Pengguna yang request-nya sedang diproses (misalnya sedang mengunggah file scan KTM atau slip bayar 15MB) akan terputus paksa di tengah jalan karena proses di container lama keburu dimatikan sebelum selesai menulis data 😭

Berbagai cara deploy tanpa downtime

Sebelum memilih docker-rollout, mari kita tinjau beberapa pendekatan umum yang biasa dipakai para engineer untuk menyelesaikan masalah ini di server tunggal:

Blue-green manual

Pendekatan ini menyiapkan dua definisi service identik di dalam compose.yml, misalnya web_blue di port 3001 dan web_green di port 3002. Saat versi blue aktif, kita menyalakan green. Begitu green siap, kita mengubah upstream di konfigurasi Nginx lalu menjalankan nginx -s reload, baru kemudian mematikan blue. Cara ini bekerja dengan baik, tetapi menuntut banyak otomasi manual, rawan salah edit konfigurasi saat panik, dan membuat file compose menjadi kembung dengan duplikasi service.

Orchestrator (Swarm / Kubernetes)

Ini adalah standar industri yang sesungguhnya. Baik Docker Swarm maupun Kubernetes memiliki abstraksi rolling update secara native: container baru dinyalakan, dicek kesehatannya, lalu container lama dihapus bertahap. Namun, memasang Swarm atau Kubernetes untuk satu VPS kecil ibarat membawa truk tronton untuk mengantar satu bungkus nasi goreng wkwkwk. Overhead memorinya terlalu besar (bisa memakan 1 sampai 3 GB RAM cluster sendiri) dan kompleksitas perawatannya tidak sebanding untuk project skala kecil hingga menengah.

Deploy tool (Kamal, Dokku, dsb)

Platform seperti Kamal (garapan 37signals) atau Dokku sudah mendukung zero-downtime out-of-the-box. Kekurangannya, tool-tool ini biasanya cukup opinionated dan meminta kita mengikuti aturan main mereka: mulai dari konfigurasi khusus, keharusan memiliki Docker Registry remote, hingga alur SSH khusus. Jika tujuan kita hanya ingin script deploy sederhana yang bisa dijalankan langsung di server dengan file compose yang sudah ada, adopsi platform baru kadang terasa terlalu merepotkan.

docker-rollout

Pendekatan ini mengambil jalan tengah yang paling pragmatis. Cukup satu perintah pengganti, tanpa daemon tambahan, tanpa mengubah format compose.yml secara drastis, dan bekerja langsung di atas engine Docker yang sudah terpasang.

Metode Setup Butuh proxy Cocok untuk
compose up -d Tidak ada Tidak Dev / hobby
Blue-green manual Sedang Ya VPS tunggal + script sendiri
Swarm / K8s Berat Bawaan Multi node, enterprise
Kamal / Dokku Sedang Bawaan Project baru dengan CI/CD
docker-rollout Ringan Ya 1 VPS / homelab, hemat budget

Kenalan dengan docker-rollout

docker-rollout adalah plugin Docker CLI open-source yang dikembangkan oleh Piotr Gaczkowski (wowu). Plugin ini ditulis murni menggunakan Shell script mandiri, berlisensi MIT, memiliki sekitar 3 ribu bintang di GitHub, dan aktif dirawat (saat artikel ini ditulis sudah mencapai versi 0.14). Tool ini bekerja mulus baik dengan docker compose (plugin v2) maupun docker-compose versi lawas.

Konsep intinya sangat sederhana tetapi cerdik. Alih-alih mematikan container lama lalu membuat yang baru, docker-rollout memanfaatkan fitur bawaan Docker Compose untuk melakukan scaling instance secara dinamis dalam 3 langkah:

  1. Scale ke 2x: Menggandakan jumlah instance service target menjadi dua kali lipat (misalnya dari 1 menjadi 2). Container baru mulai booting secara paralel, sementara container lama tetap aktif melayani pengguna.
  2. Tunggu Sehat: Menunggu container baru mencapai status healthy (jika healthcheck didefinisikan) atau menunggu timer tertentu sampai aplikasi dipastikan siap menerima traffic.
  3. Hapus yang Lama: Menghentikan dan menghapus container versi lama secara bertahap, menyisakan container versi baru yang kini sepenuhnya mengambil alih beban kerja.
Transisi 3 Tahap docker-rollout
Transisi 3 tahap zero-downtime deployment pada docker-rollout
Catatan: Karena mekanisme scaling ini memanfaatkan penamaan bawaan Docker Compose, nomor pada nama container akan bertambah terus setiap kali deploy (misalnya dari web-1 ke web-2, lalu web-3, dan seterusnya). Itu perilaku normal dan memang cara Docker Compose membedakan instance yang berbeda.

Persyaratan

Sebelum mulai menerapkan docker-rollout, pastikan lingkungan server kamu memenuhi prasyarat arsitektur berikut:

  • VPS / mesin dengan Docker dan Docker Compose plugin: Disarankan menggunakan Docker Compose modern (perintah docker compose, bukan docker-compose lama, walau dua-duanya tetap didukung).
  • Reverse proxy dinamis: Karena selama masa transisi akan ada dua container yang hidup bersamaan, kamu wajib menggunakan reverse proxy yang bisa mendeteksi container Docker secara otomatis lewat label atau Docker network, seperti Traefik, Nginx Proxy (nginx-proxy), atau Caddy Docker Proxy. Di artikel ini kita akan memakai Traefik sebagai contoh utama, sisanya cukup disesuaikan.
  • Service target tidak boleh punya container_name:: Docker melarang keras dua container memiliki nama yang persis sama. Hapus baris container_name: di service kamu agar Docker Compose bisa mengelola penamaan otomatis (web-1, web-2).
  • Service target tidak boleh punya ports: langsung: Hapus baris pemetaan port host seperti ports: ["3000:3000"] pada service web. Jika dua container mencoba membinding port yang sama di host secara bersamaan, container kedua akan langsung gagal start dengan error address already in use. Semua traffic dari luar harus dialirkan melalui reverse proxy.
  • Endpoint healthcheck yang murah: Aplikasi sebaiknya menyediakan endpoint pengecekan kondisi kesehatan (misalnya GET /health atau GET /api/health) yang cepat dan hemat resource.
  • (Opsional) Graceful shutdown: Aplikasi sebaiknya menangani sinyal SIGTERM untuk menyelesaikan proses yang sedang aktif sebelum proses dimatikan.
Peringatan: Bagian berikutnya sangat teknis. Diasumsikan kamu sudah nyaman mengoperasikan terminal Linux, Docker Compose, dan konsep dasar reverse proxy.

Instalasi docker-rollout

Pemasangan docker-rollout sangat ringkas karena berupa script mandiri yang langsung dipasang ke folder plugin CLI Docker:

BASH
# Buat direktori plugin docker jika belum ada
mkdir -p ~/.docker/cli-plugins

# Unduh script docker-rollout langsung dari repository resminya
curl https://raw.githubusercontent.com/wowu/docker-rollout/main/docker-rollout \
  -o ~/.docker/cli-plugins/docker-rollout

# Berikan izin eksekusi
chmod +x ~/.docker/cli-plugins/docker-rollout

Jika kamu sering menjalankan Docker menggunakan sudo, atau ingin plugin ini dapat diakses oleh seluruh pengguna di server, pasang script tersebut ke path global /usr/local/lib/docker/cli-plugins/docker-rollout.

Cek apakah sudah terpasang:

BASH
docker rollout --help

Jika instalasi berhasil, terminal akan menampilkan opsi bantuan dari perintah docker rollout. Kalau yang muncul pesan "docker: 'rollout' is not a docker command", jangan lanjut dulu! Cek kembali apakah path foldernya sudah tepat dan izin eksekusi (chmod +x) sudah diberikan.

Setup

Proses setup terdiri dari empat bagian utama. Pastikan kamu menyelesaikan checkpoint di setiap langkahnya sebelum melangkah ke bagian berikutnya:

1. Pasang reverse proxy

Siapkan reverse proxy (di sini kita gunakan Traefik) yang terhubung ke satu Docker network eksternal bersama bernama proxy. Berikut contoh ringkas file compose.yml untuk Traefik:

YAML
services:
  traefik:
    image: traefik:v3.0
    restart: always
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entryPoints.web.address=:80"
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy

networks:
  proxy:
    name: proxy

Cek: Jalankan Traefik dengan docker compose up -d. Pastikan container Traefik berstatus running dan network proxy muncul saat kamu mengetikkan docker network ls.

2. Siapkan compose.yml service

Sekarang buka file compose.yml aplikasi kamu. Buang baris container_name dan pemetaan ports, lalu pasang routing label Traefik serta daftarkan service ke network proxy.

Sebelum (konfigurasi lama yang rawan 502):

YAML
services:
  web:
    build: .
    container_name: web-app       # WAJIB DIHAPUS!
    ports:
      - "3000:3000"               # WAJIB DIHAPUS!

Sesudah (siap rollout):

YAML
services:
  web:
    build: .
    restart: always
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.web.rule=Host(`lomba.kampus.ac.id`)"
      - "traefik.http.routers.web.entrypoints=web"
      - "traefik.http.services.web.loadbalancer.server.port=3000"
    healthcheck:
      test: curl -f http://localhost:3000/health || exit 1
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 10s

networks:
  proxy:
    external: true

Cek: Jalankan aplikasi sekali dengan docker compose up -d web. Akses domain kamu lewat browser atau curl; pastikan sudah merespons dengan kode HTTP 200.

3. Tambahkan healthcheck

Healthcheck adalah kunci paling krusial dalam mekanisme zero-downtime. Tanpa healthcheck, docker-rollout tidak punya cara untuk mengetahui apakah aplikasi baru sudah selesai inisialisasi koneksi database atau masih cold start. Akibatnya, rollout hanya akan menunggu pasif selama sleep 10 detik default, yang belum tentu cukup bagi aplikasi berat.

YAML
healthcheck:
  test: curl -f http://localhost:3000/health || exit 1
  interval: 5s
  timeout: 3s
  retries: 3
  start_period: 10s

Jika image aplikasi kamu berbasis minimal tanpa curl (seperti Alpine murni atau Distroless), gunakan wget -qO- http://localhost:3000/health || exit 1 atau sertakan binary healthcheck kecil.

Cek: Jalankan perintah docker ps di terminal. Kolom STATUS pada container kamu harus menampilkan keterangan (healthy) setelah durasi start_period terlewati.

4. Rollout pertama

Saatnya menguji coba. Sekarang, gantikan perintah lama docker compose up -d web dengan perintah rollout:

BASH
docker rollout web

Tempelkan perhatianmu ke log output terminal yang dihasilkan:

TEXT
Scaling service web to 2 instances...
Waiting for new container to become healthy...
Container web-2 is healthy!
Stopping and removing old container web-1...
Rollout completed successfully in 12s.

Untuk membuktikan bahwa tidak ada downtime sama sekali, buka tab terminal kedua sebelum kamu menjalankan perintah rollout, lalu tembak domain kamu secara terus-menerus setiap 0.5 detik:

BASH
while true; do
  curl -s -o /dev/null -w "%{http_code}\n" https://lomba.kampus.ac.id
  sleep 0.5
done

Lihat hasilnya: seluruh baris output di terminal kedua akan tetap stabil menampilkan angka 200 dari awal proses scaling sampai container lama terhapus sempurna!

Beberapa opsi rollout yang sering dipakai:

  • -f | --file PATH: Menentukan path ke file compose jika namanya bukan compose.yml standar.
  • -t | --timeout DETIK: Batas waktu menunggu container baru menjadi healthy (default: 60 detik).
  • -w | --wait DETIK: Waktu tunggu jika container tidak memiliki healthcheck (default: 10 detik).
  • --wait-after-healthy DETIK: Jeda waktu tambahan setelah container baru sehat sebelum container lama mulai dimatikan.
  • --env-file PATH: Menentukan file environment khusus.

Draining: jangan buang request yang sedang jalan

Berhasil mendapatkan kode HTTP 200 terus-menerus baru menyelesaikan separuh masalah. Masih ada masalah kedua yang tidak kalah menyakitkan: in-flight request.

Bayangkan ada peserta lomba yang sedang mengunggah berkas zip portofolio sebesar 20MB di container web-1. Tepat ketika web-2 dinyatakan healthy oleh Docker, docker-rollout secara default akan langsung mematikan web-1. Traefik memang pintar langsung mengarahkan pengunjung baru ke web-2, tetapi koneksi upload yang sedang berjalan di web-1 akan terputus seketika di tengah jalan!

Solusi resminya adalah menerapkan mekanisme connection draining. Caranya adalah dengan membuat healthcheck yang sengaja gagal jika ada file penanda /tmp/drain:

YAML
services:
  web:
    # ... konfigurasi lainnya ...
    healthcheck:
      test: test ! -f /tmp/drain && curl -f http://localhost:3000/health
      interval: 5s
      retries: 1
    labels:
      - "docker-rollout.pre-stop-hook=touch /tmp/drain && sleep 10"

Urutan kejadian yang berlangsung di balik layar:

Alur Connection Draining docker-rollout
Alur kerja connection draining menggunakan penanda /tmp/drain dan pre-stop hook

Selain itu, pastikan juga aplikasi backend kamu menangani sinyal SIGTERM dengan baik untuk melakukan graceful shutdown (misalnya menutup server HTTP dan menunggu koneksi aktif selesai diproses sebelum benar-benar keluar):

JAVASCRIPT
process.on('SIGTERM', async () => {
  console.log('Menerima sinyal SIGTERM, bersiap shutdown...');
  server.close(() => {
    console.log('Seluruh koneksi HTTP aktif selesai diproses. Keluar.');
    process.exit(0);
  });
});

Cek: Jalankan request yang memakan waktu lama (misalnya endpoint simulasi sleep 5 detik) tepat saat kamu mengeksekusi rollout. Request tersebut harus tetap selesai dengan selamat dan menghasilkan kode 200.

Script deploy lengkap

Untuk menyatukan seluruh alur ini ke dalam alur kerja produksi harian di server, bungkus ke dalam satu file script bash sederhana, misalnya deploy.sh:

BASH
#!/usr/bin/env bash
set -euo pipefail

echo "==> Mengambil kode terbaru..."
git pull

echo "==> Membangun image baru..."
docker compose build web        # atau: docker compose pull web

echo "==> Menjalankan migrasi database..."
docker compose run --rm web npm run db:migrate

echo "==> Melakukan zero-downtime rollout..."
docker rollout web --timeout 60

echo "==> Selesai! Deploy sukses tanpa downtime."
Catatan: Saat rollout berlangsung, sempat ada dua versi aplikasi yang berjalan bersamaan di waktu yang sama. Oleh karena itu, migrasi database kamu harus bersifat backward-compatible. Menambah kolom baru adalah tindakan aman. Tetapi menghapus kolom, mengubah nama kolom, atau mengubah tipe data kolom harus dipecah ke dalam fase rilis terpisah agar versi aplikasi lama tidak crash saat rollout masih berlangsung.

Opsional: Script ini bisa dipanggil secara otomatis dari pipeline CI/CD seperti GitHub Actions via SSH atau webhook server pribadi.

Catatan dan batasan

Agar ekspektasi tetap realistis, ada beberapa batasan teknis dari docker-rollout yang wajib diperhatikan:

  • Wajib proxy, tidak bisa ports: langsung: Tanpa reverse proxy dinamis, kamu tidak bisa menjalankan dua instance container di host yang sama.
  • Satu service per perintah: Perintah docker rollout hanya menerima satu service dalam sekali jalan. Jika memiliki beberapa service web yang saling berhubungan, lakukan pemanggilan berurutan di script.
  • Bukan rollback otomatis: Jika container baru gagal melewati pengecekan healthcheck dalam batas waktu timeout, container lama memang tidak dimatikan (layanan tetap hidup). Namun proses deploy akan berhenti dengan status error dan kamu tetap harus memeriksa log secara manual.
  • Bukan untuk stateful service: Jangan pernah menggunakan rollout pada database seperti PostgreSQL, MySQL, Redis, atau antrean RabbitMQ. Scaling pada database tunggal akan merusak integritas data lock.
  • RAM/CPU sementara naik 2x saat deploy: Pastikan VPS kamu memiliki sisa headroom memori yang cukup agar container baru tidak terkena bunuh oleh Out-of-Memory (OOM) Killer.
  • Kalau sudah multi node, ini saatnya lirik Swarm / K8s beneran: docker-rollout dirancang khusus untuk satu mesin Docker Compose. Jika arsitektur kamu sudah berkembang ke beberapa server cluster, beralihlah ke orchestrator multi-node sejati.

Penutup

Success cheers
Perasaan developer waktu deploy web lomba berhasil mulus tanpa ada peserta yang ngamuk di grup wkwkwk πŸ•ΊπŸ₯‚

Kombinasi antara reverse proxy dinamis, healthcheck yang tepat, dan docker-rollout membuktikan bahwa kita bisa mencapai deployment tanpa jeda 502 tanpa harus mengorbankan kesederhanaan stack Docker Compose yang sudah ada.

Bagi saya pribadi, workflow deployment di server side project dan homelab kini jauh lebih tenang. Tidak ada lagi rasa was-was saat harus menekan tombol deploy di jam-jam sibuk, dan peserta lomba bisa menyelesaikan pendaftaran mereka dengan nyaman tanpa terganggu error teknis.

Silakan coba terapkan di salah satu service web kamu terlebih dahulu, perhatikan bagaimana transisi scaling-nya bekerja, lalu tambahkan konfigurasi draining saat kamu sudah siap masuk ke lingkungan produksi.

Referensi

  • Repository GitHub: wowu/docker-rollout
  • Dokumentasi resmi docker-rollout
  • Dokumentasi Traefik Docker Provider

Comments

  • About
  • Contact
  • Projects
  • Blog

Here We Go

  • Designed withFigma
  • Developed withNext.js
  • Backend withHygraph