# Zero-Downtime Deployment Menggunakan Docker Swarm dan Caddy Proxy

> Diterbitkan oleh Literally Web ID pada 21 Oct 2026
> Sumber: https://literally.web.id/articles/zero-downtime-deployment-menggunakan-docker-swarm-dan-caddy-proxy

---

# Zero-Downtime Deployment Menggunakan Docker Swarm dan Caddy Proxy

## 1. Titik Awal Masalah: Mengapa Arsitektur Lama Sering Ambruk di Produksi

Ingat malam jumat tiga tahun lalu ketika jam 2 pagi aplikasi web utama kita tumbang hanya karena `git pull` dan restart layanan PHP-FPM serta Nginx secara bersamaan? Pengguna mendadak disambut halaman *502 Bad Gateway*, keranjang belanja online lenyap seketika, dan chat dari C-level mulai berderit masuk di Slack. Di dunia nyata, downtime bukan sekadar angka statistik SLA; itu adalah uang yang terbakar dan kepercayaan pelanggan yang terkikis.

Masalah klasik ini biasanya berakar dari manajemen state dan proses *rolling* yang naif. Kita sering kali memperlakukan server produksi seperti mesin jahit tua: dimatikan dulu, benangnya diganti, baru dinyalakan lagi. Padahal, arsitektur web modern—yang ditopang oleh backend Node.js atau Go, frontend yang dirender via Next.js, database MariaDB, serta caching layer Redis—menuntut ketersediaan tanpa kompromi. Ketika traffic melonjak akibat kampanye pemasaran, satu detik gangguan saja bisa merontokkan ribuan transaksi.

Kita butuh cara agar pembaruan kode dan infrastruktur bisa meluncur mulus bagai pisau bedah tanpa mengusik satu pun pengguna yang sedang aktif berselancar. Perjalanan kita menuju *zero-downtime deployment* sejati dimulai dari bagaimana cara kita membiarkan kontainer berbicara dengan dunia luar tanpa jeda. Mari kita bedah fondasi yang membuat keajaiban ini mungkin terjadi.

## 2. Di Balik Layar: Bagaimana Sistem Ini Bekerja Tanpa Sihir

Tidak ada sihir di sini; yang ada hanyalah orkestrasi cerdas antara Docker Swarm dan Caddy Proxy yang bekerja bahu-membahu. Bayangkan Caddy sebagai resepsionis hotel bintang lima yang sangat efisien dan ramah SSL, sementara Docker Swarm adalah manajemen gedung yang bisa membangun kamar baru dan merobohkan kamar lama secara instan tanpa tamu menyadari kamarnya sedang direnovasi.

Ketika kita memicu *rolling update* di Docker Swarm, Swarm tidak langsung mematikan semua kontainer lama. Ia menggunakan strategi *parallelism* dan *delay*. Swarm meluncurkan kontainer versi baru (misalnya v2), memasangnya ke dalam jaringan internal, lalu menunggu hingga kontainer tersebut benar-benar siap menerima traffic—berkat konfigurasi *health check probe* yang presisi. Setelah kontainer v2 lolos uji kesehatan, barulah Swarm dengan anggun mencopot dan mematikan kontainer v1.

Di saat yang sama, Caddy Proxy bertindak sebagai reverse proxy yang dinamis. Berkat modul resolver internal Docker, Caddy secara otomatis mendeteksi perubahan IP container backend yang bergeser tanpa perlu di-reload secara manual (`nginx -s reload` adalah cerita lama yang rawan race condition). Kombinasi ini menciptakan jalur pipa (*pipeline*) pengiriman yang kedap udara. Penasaran bagaimana kita menimbang pilihan alat tempurnya? Mari kita lihat kompromi arsitektural di bab berikutnya.

## 3. Pilihan Pendekatan & Kompromi yang Harus Diambil

Dalam dunia infrastruktur skala besar, tidak ada peluru perak. Pilihan selalu datang dengan harga yang harus dibayar. Saat merancang sistem *zero-downtime*, kita dihadapkan pada persimpangan antara kompleksitas Kubernetes dan kesederhanaan Docker Swarm.

Kubernetes (K8s) menawarkan fleksibilitas tanpa batas, namun kurva pembelajarannya curam dan membutuhkan sumber daya operasional yang tidak sedikit hanya untuk *control plane*-nya. Sebaliknya, Docker Swarm sudah tertanam langsung di dalam engine Docker, sangat ringan, dan memakan waktu setup hitungan menit, bukan hari. Untuk tim dengan skala menengah yang ingin stabilitas tingkat tinggi tanpa overhead manajemen cluster yang rumit, Docker Swarm adalah pemenang mutlak.

Sementara itu, untuk urusan reverse proxy, kita bisa memilih antara Nginx berbayar (Plus) untuk fitur *dynamic upstream* atau Caddy Proxy open-source yang secara native mendukung pembaruan konfigurasi otomatis dan manajemen SSL Let's Encrypt secara *out-of-the-box*. Kita memilih Caddy karena ia menghilangkan kerumitan pengelolaan sertifikat SSL manual yang sering menjadi titik kegagalan manusia (*human error*). Sebelum kita mengetikkan baris kode pertama, mari siapkan amunisi dan lingkungannya.

## 4. Meracik Lingkungan: Persiapan & Prasyarat Lapangan

Sebelum kita menyentuh orkestrasi kontainer, pastikan fondasi mesin virtual atau bare-metal kita sudah siap tempur. Kita asumsikan Anda menggunakan dua atau tiga buah server Ubuntu 22.04 LTS yang terhubung dalam jaringan lokal yang aman, lengkap dengan DNS Record A yang mengarah ke IP publik node utama.

Pertama, pastikan Docker dan Docker Compose versi terbaru terinstal di semua node. Inisialisasi Swarm pada node utama (Manager) dengan perintah berikut:

```bash
docker swarm init --advertise-addr <IP_PUBLIK_ATAU_PRIVAT_MANAGER>
```

Simpan token yang dihasilkan, lalu jalankan perintah `docker swarm join` tersebut pada node worker lainnya. Pastikan status cluster sehat dengan mengetikkan `docker node ls`. 

> [!IMPORTANT]
> Pastikan port TCP 2377 (Cluster management), TCP/UDP 7946 (Container network discovery), dan UDP 4789 (Overlay network) terbuka pada firewall Anda agar komunikasi antar node di dalam Swarm berjalan tanpa hambatan.

Selanjutnya, kita akan menyusun orkestrasi aplikasi menggunakan file definisi layanan yang menyatukan backend web, database MySQL/MariaDB, caching Redis, dan Caddy Proxy dalam satu kesatuan harmonis.

## 5. Langkah Implementasi: Membangun Solusi yang Tangguh

Inilah jantung dari seluruh arsitektur kita: file `docker-stack.yml`. File ini bukan sekadar konfigurasi Docker Compose biasa; ia mendefinisikan *deploy constraints*, *update configs*, dan *health checks* yang menjadi kunci utama zero-downtime.

Buat file konfigurasi stack Anda dengan struktur seperti di bawah ini:

```yaml
version: '3.8'

networks:
  webnet:
    driver: overlay
  internal:
    driver: overlay

services:
  caddy:
    image: caddy:2.7-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - caddy_data:/data
      - caddy_config:/config
      - ./Caddyfile:/etc/caddy/Caddyfile
    networks:
      - webnet
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]

  app:
    image: registry.domain.internal/my-web-app:v1.0.0
    environment:
      - DB_HOST=database
      - REDIS_HOST=redis
    networks:
      - webnet
      - internal
    deploy:
      mode: replicated
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        failure_action: rollback
      rollback_config:
        parallelism: 1
        order: stop-first
      restart_policy:
        condition: on-failure
      healthcheck:
        test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
        interval: 10s
        timeout: 3s
        retries: 3
        start_period: 15s

volumes:
  caddy_data:
  caddy_config:
```

Perhatikan blok `update_config` dan `healthcheck` di atas. Kombinasi `order: start-first` dan pemeriksaan endpoint `/health` adalah rahasia dapur mengapa deployment kita tidak akan pernah memutus koneksi pengguna aktif. Mari kita bedah baris-baris krusial ini agar Anda paham betul mekanismenya.

## 6. Membedah Baris Kode Krusial yang Kerap Menjebak

Banyak engineer gagal menerapkan zero-downtime karena salah memahami parameter `update_config` di Docker Swarm. Mari kita telaah secara mendalam.

Pertama, parameter `order: start-first` memerintahkan Swarm untuk menyalakan kontainer baru *terlebih dahulu* sebelum menyentuh atau mematikan kontainer lama. Kebalikan dari ini adalah `stop-first`, yang justru akan mematikan layanan lama sebelum yang baru siap—sebuah resep pasti untuk memicu error 502.

Kedua, parameter `healthcheck` sangat menentukan validasi kesiapan aplikasi:

```yaml
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 10s
  timeout: 3s
  retries: 3
  start_period: 15s
```

Di sini, `start_period: 15s` memberikan napas kepada aplikasi backend (baik itu Node.js, Go, atau PHP-FPM di belakang Swoole) untuk melakukan inisialisasi koneksi database, memuat cache Redis, dan melakukan *warm-up*. Jika dalam waktu tersebut endpoint `/health` belum merespons dengan HTTP status 200, Swarm akan menganggap deployment gagal dan otomatis melakukan *rollback* ke versi stabil sebelumnya.

Lalu, bagaimana dengan Caddy Proxy? File `Caddyfile` kita sangat sederhana namun mematikan:

```text
app.domain.com {
    reverse_proxy app:3000 {
        lb_policy round_robin
        health_uri /health
        health_interval 5s
    }
}
```

Caddy secara otomatis mendeteksi nama layanan `app` di dalam overlay network Docker Swarm, melakukan load balancing ke semua replika yang sehat, dan menangani pembaruan sertifikat SSL Let's Encrypt secara transparan. Namun, bagaimana jika sistem diguyur traffic ribuan request per detik saat proses pembaruan berjalan? Mari kita uji di medan perang.

## 7. Bertahan di Bawah Tekanan: Skenario Traffic Nyata

Teori selalu indah di atas kertas. Bayangkan skenario nyata: platform e-commerce Anda sedang dibanjiri 5.000 request per detik saat *flash sale*, dan tim produk mendadak mengirimkan hotfix kritis untuk memperbaiki bug pembayaran.

Anda memicu perintah pembaruan:

```bash
docker stack deploy -c docker-stack.yml production_stack
```

Di balik layar, Docker Swarm mengganti replika pertama dari `v1.0.0` ke `v1.0.1`. Caddy Proxy yang mendeteksi perubahan health status secara real-time langsung mengalihkan sebagian beban traffic ke replika baru yang sudah lolos uji kesehatan. Dua replika `v1.0.0` lainnya tetap melayani sisa traffic jutaan pengguna tanpa hambatan.

Tidak ada dropped connection, tidak ada error timeout, dan database MySQL tidak mengalami lonjakan koneksi mendadak karena proses *rolling* berjalan secara bertahap (`parallelism: 1`). Pengguna yang sedang melakukan *checkout* tetap dapat menyelesaikan pembayarannya dengan mulus. Kendati demikian, jurang kegagalan selalu mengintai jika kita lengah terhadap beberapa detail kecil.

## 8. Jebakan Klasik & Cara Menghindarinya Sebelum Meledak

Pengalaman bertahun-tahun mengawal infrastruktur produksi mengajarkan saya bahwa bencana sering kali datang dari hal-hal sepele yang diabaikan. Berikut adalah tiga jebakan mematikan yang harus Anda hindari:

1. **Aplikasi Belum Siap Menerima Traffic Saat Port Terbuka**: Seringkali, sebuah aplikasi web mencatat port TCP-nya sudah terbuka (sehingga dianggap sehat oleh sistem), padahal koneksi ke database atau Redis masih dalam proses *handshake*. Solusinya: Selalu gunakan `start_period` yang cukup pada health check dan pastikan endpoint `/health` benar-benar melakukan *ping* ke database secara internal.
2. **Ketiadaan *Graceful Shutdown* di Level Kode Aplikasi**: Ketika kontainer lama dimatikan oleh Swarm, jika aplikasi backend Anda langsung menutup proses tanpa menyelesaikan *request* yang sedang berjalan, pengguna akan mendapati koneksi terputus di tengah jalan. Pastikan kode aplikasi Anda mendengarkan sinyal `SIGTERM` dan memberi waktu beberapa detik untuk merampungkan request aktif.
3. **Kapasitas Resource Node yang Terlalu Pas-pasan**: Karena strategi `start_first` membuat kontainer lama dan kontainer baru berjalan secara bersamaan dalam satu jendela waktu, pastikan node Anda memiliki cadangan RAM dan CPU minimal 40% untuk menampung lonjakan beban sementara tersebut.

Sebelum Anda merilis sistem ini ke lingkungan produksi yang sesungguhnya, mari kita lakukan uji ketahanan terakhir.

## 9. Uji Nyali: Mengukur Performa di Titik Batas

Insinyur infrastruktur yang baik tidak pernah percaya begitu saja pada konfigurasi sebelum diuji di bawah tekanan ekstrem. Kita perlu mensimulasikan beban tinggi saat proses deployment sedang berjalan menggunakan utilitas pengujian beban seperti `k6` atau `hey`.

Jalankan script pengujian k6 sederhana untuk menghantam domain aplikasi Anda secara konstan:

```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 200 },
    { duration: '1m', target: 500 },
    { duration: '30s', target: 0 },
  ],
};

export default function () {
  const res = http.get('https://app.domain.com/');
  check(res, {
    'status is 200': (r) => r.status === 200,
  });
  sleep(0.1);
}
```

Sambil skrip `k6` ini menghujani server dengan ratusan request per detik, jalankan pembaruan stack Docker Anda. Amati metrik latensi dan tingkat error (jika ada) melalui dashboard monitoring Anda. Jika persentase error tetap berada di angka 0.00% dan latensi stabil, selamat—infrastruktur Anda kini telah resmi teruji memiliki ketahanan tingkat industri.

## 10. Catatan Lapangan & Checklist Sebelum Rilis

Sebagai penutup dari perjalanan teknis kita kali ini, simpan checklist operasional berikut di kepala atau wiki tim Anda sebelum menekan tombol *deploy* di produksi:

- [ ] Pastikan DNS Record A dan CNAME sudah stabil dan TTL diturunkan sementara jika diperlukan.
- [ ] Verifikasi bahwa direktori volume Caddy (`caddy_data`) terpetakan dengan benar agar sertifikat SSL tidak terhapus saat kontainer di-restart.
- [ ] Uji endpoint `/health` secara manual menggunakan `curl` dari dalam container Caddy untuk memastikan jalur komunikasi internal bersih dari masalah jaringan.
- [ ] Pastikan strategi `update_config` sudah diatur dengan opsi `order: start-first` dan `failure_action: rollback`.
- [ ] Siapkan kanal notifikasi (Telegram, Slack, atau Discord) yang terhubung dengan webhook Docker Swarm atau sistem pemantauan untuk mendeteksi kegagalan secara dini.

Dengan arsitektur ini, tidur malam Anda tidak akan lagi terganggu oleh kepanikan saat pembaruan kode. Infrastruktur yang tangguh bukan berarti tidak pernah menghadapi masalah, melainkan dirancang untuk menyerap dan mengatasi masalah tersebut secara elegan tanpa disadari oleh pengguna.