L
Microservices • Literally Web ID

Arsitektur Zero-Downtime Deployment Menggunakan Docker, Nginx, dan GitHub Actions

⏱️ 18 menit baca • 📅 16 Oct 2026 • 👁️ 1 views
Arsitektur Zero-Downtime Deployment Menggunakan Docker, Nginx, dan GitHub Actions

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap pengembangan aplikasi web modern berbasis microservices maupun monolith skala besar, kebutuhan akan ketersediaan layanan 24/7 (high availability) bukan lagi sekadar fitur tambahan, melainkan sebuah kewajiban absolut (business-critical SLA). Bayangkan sebuah platform e-commerce enterprise atau layanan finansial yang mengalami gangguan akses selama 5 hingga 10 menit setiap kali ada pembaruan kode (code deployment). Dampaknya sangat masif: penurunan konversi transaksi secara langsung, merosotnya kepercayaan pengguna, hingga potensi kerugian finansial yang signifikan.

Secara historis, praktik deployment konvensional sering kali mengandalkan pendekatan rolling update sederhana atau penghentian total layanan sementara (maintenance window). Pada arsitektur tradisional, urutan eksekusinya biasanya seperti ini:

  1. Mengunduh kode atau biner baru.
  2. Mematikan proses aplikasi yang sedang berjalan (stop service).
  3. Melakukan migrasi database atau pembaruan dependensi.
  4. Menyalakan kembali proses aplikasi (start service).

Pendekatan ini meninggalkan celah kegagalan yang lebar. Jika proses startup gagal karena kesalahan konfigurasi atau terjadi inkonsistensi dependensi, sistem akan mengalami downtime total sampai tim teknik melakukan intervensi manual. Belum lagi masalah dropped connections, di mana klien yang sedang melakukan transaksi atau mengirim data mendadak menerima kode status HTTP 502 Bad Gateway atau koneksi terputus secara paksa di tengah jalan.

Perbandingan Kondisi: Sebelum vs Sesudah Penerapan Zero-Downtime

Parameter Evaluasi Sebelum (Deploy Konvensional / Downtime) Sesudah (Zero-Downtime Blue-Green & Docker) Dampak Bisnis & Operasional
Waktu Henti (Downtime) 2 – 15 menit per rilis 0 detik (Seamless transition) SLA tercapai 99.99%, tanpa interupsi pengguna
Penanganan Koneksi Koneksi aktif langsung terputus (dropped) Graceful draining hingga selesai Zero lost requests atau transaksi gagal
Kecepatan Rollback 10 – 30 menit (manual restore/redeploy) < 5 detik (switch Nginx upstream pointer) Mitigasi risiko insiden produksi sangat cepat
Keterlibatan Manusia Tinggi (butuh pengawasan manual) Sepenuhnya otomatis via CI/CD pipeline Mengurangi human error secara drastis
Konsumsi Sumber Daya Rendah saat idle, lonjakan saat restart Stabil (menjalankan dua environment paralel) Kebutuhan kapasitas RAM/CPU sedikit lebih tinggi

Dengan mengintegrasikan kontainerisasi Docker, reverse proxy Nginx, dan otomatisasi GitHub Actions, kita dapat merancang sebuah ekosistem Zero-Downtime Deployment yang tidak hanya andal, tetapi juga dapat direplikasi dengan mudah di berbagai lingkungan server produksi.


2. Konsep Inti & Deep Dive Arsitektur

Arsitektur Zero-Downtime Deployment yang akan kita bedah menggunakan pendekatan Blue-Green Deployment yang dikombinasikan dengan manajemen upstream dinamis pada Nginx dan orkestrasi kontainer menggunakan Docker Compose.

Alur Eksekusi di Balik Layar (Under the Hood)

Sistem ini bekerja dengan memelihara dua lingkungan instans aplikasi yang identik secara fungsional namun terisolasi secara port dan nama kontainer, dinamakan Blue dan Green.

  1. Kondisi Awal (Steady State): Lingkungan Blue sedang aktif melayani seluruh incoming traffic dari Nginx. Kontainer Green berada dalam kondisi mati atau siaga (standby).
  2. Triger Pembaruan: Developer mendorong kode baru ke branch utama di GitHub. GitHub Actions mendeteksi perubahan dan memicu pipeline CI/CD.
  3. Build & Push: GitHub Actions membangun citra Docker (Docker image) baru dan menyimpannya ke container registry lokal atau privat.
  4. Deploy Environment Pasif (Green): Pipeline masuk ke server produksi melalui SSH, lalu menarik citra terbaru dan menjalankan kontainer Green pada port internal yang berbeda (misal: port 8081).
  5. Health Check Otomatis: Sebelum Nginx dialihkan, sistem melakukan pengujian kesehatan mandiri (health check) dengan memanggil endpoint /healthz pada kontainer Green. Jika mengembalikan HTTP 200 OK, proses dilanjutkan.
  6. Traffic Switching & Graceful Reload: Nginx dikonfigurasi ulang untuk mengarahkan lalu lintas ke port kontainer Green. Nginx melakukan reload konfigurasi secara mulus (nginx -s reload) tanpa mematikan koneksi yang sedang berlangsung pada kontainer Blue.
  7. Cleanup: Kontainer Blue lama dihentikan dan dibersihkan untuk menghemat sumber daya, bersiap menjadi lingkungan pasif pada siklus deployment berikutnya.
[ Client / Browser ]
         │
         ▼
┌─────────────────────────────────────────┐
│         Nginx Reverse Proxy             │
│   (Dynamic Upstream / Active-Passive)   │
└────────────────────┬────────────────────┘
                     │
         ┌───────────┴───────────┐
         ▼                       ▼
┌─────────────────┐     ┌─────────────────┐
│  Container Blue │     │ Container Green │
│   (Port 8080)   │     │   (Port 8081)   │
│   [INACTIVE]    │     │    [ACTIVE]     │
└─────────────────┘     └─────────────────┘

3. Perbandingan Komparatif / Analisis Pendekatan

Dalam membangun sistem deployment tingkat lanjut, terdapat beberapa pendekatan arsitektural yang sering dipertimbangkan oleh para System Architect. Berikut adalah analisis mendalam mengenai perbandingan antara Blue-Green Deployment, Rolling Update, dan Canary Deployment.

Aspek Perbandingan Blue-Green Deployment Rolling Update (Kubernetes / Docker Swarm) Canary Deployment
Kompleksitas Implementasi Menengah (Butuh manajemen upstream proxy) Tinggi (Butuh orkestrator seperti K8s) Tinggi (Butuh traffic shaping / service mesh)
Konsumsi Sumber Daya Membutuhkan 2x kapasitas server (sementara) Efisien (Mengganti pod/kontainer satu per satu) Efisien (Routing persentase kecil traffic)
Waktu Transisi Instan (Sekali switch pointer proxy) Bertahap (Bergantung jumlah instans) Bertahap (Dipantau berdasarkan metrik error)
Risiko Kegagalan Rilis Sangat rendah (Rollback instan) Sedang (Versi lama dan baru berjalan bersamaan) Rendah (Hanya berdampak pada sebagian kecil user)
Cocok Untuk Monolith, API backend, Web Apps standar Arsitektur microservices skala besar Aplikasi enterprise dengan risiko bisnis tinggi

[!TIP] Untuk infrastruktur VPS tunggal atau klaster kecil tanpa kerumitan Kubernetes, kombinasi Docker + Nginx + GitHub Actions dengan pola Blue-Green adalah solusi paling pragmatis, stabil, dan mudah dipelihara.


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum masuk ke tahap implementasi teknis, pastikan server produksi Anda (disarankan menggunakan Ubuntu 22.04 LTS atau Debian 12) telah memenuhi spesifikasi dan komponen perangkat lunak berikut:

  • OS: Ubuntu Server 22.04 LTS (RAM minimal 2GB, CPU 2 Core).
  • Docker Engine: Versi 24.0+ dan Docker Compose v2.
  • Web Server: Nginx (versi 1.18+).
  • Akses Jaringan: Port 80 (HTTP) dan 443 (HTTPS) terbuka. Port internal aplikasi (misal: 8080, 8081) tertutup dari akses publik eksternal (hanya dapat diakses melalui localhost/Nginx).
  • Hak Akses: Akun pengguna non-root dengan akses sudo dan tergabung dalam grup docker.

Verifikasi instalasi dasar pada server:

# Verifikasi versi Docker dan Docker Compose
docker --version
docker compose version

# Verifikasi status Nginx
sudo systemctl status nginx

5. Panduan Implementasi Langkah demi Langkah

Langkah 1: Struktur Direktori Proyek

Buat struktur direktori di server dan repositori lokal Anda dengan susunan seperti ini:

my-project/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── src/
│   └── index.php (atau server.js / main.go)
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
└── switch-env.sh

Langkah 2: Konfigurasi Dockerfile

Berikut adalah Dockerfile yang efisien dan mendukung optimasi multi-stage build untuk aplikasi backend (contoh menggunakan Node.js/Express, sesuaikan dengan stack Anda):

# --- STAGE 1: Build Dependencies --- 
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# --- STAGE 2: Production Runtime ---
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .

EXPOSE 3000
USER node
CMD ["node", "src/index.js"]

Langkah 3: Konfigurasi Docker Compose Dinamis

Kita menggunakan satu file docker-compose.yml yang mendukung variabel lingkungan untuk menentukan apakah layanan sedang berada di slot blue atau green.

version: '3.8'

services:
  app:
    image: ${DOCKER_IMAGE}:${TAG}
    container_name: app_${DEPLOY_ENV}
    restart: always
    environment:
      - NODE_ENV=production
      - PORT=3000
    ports:
      - "${HOST_PORT}:3000"
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://localhost:3000/healthz').then(r => r.ok ? process.exit(0) : process.exit(1))"]
      interval: 10s
      timeout: 5s
      retries: 3

Langkah 4: Skrip Pengalihan Nginx Upstream (switch-env.sh)

Skrip otomatisasi ini bertugas mendeteksi environment aktif saat ini, menaikkan environment seberangnya, melakukan health check, dan memperbarui konfigurasi Nginx.

#!/usr/bin/env bash
set -e

# Konfigurasi dasar
PROJECT_DIR="/var/www/my-project"
cd $PROJECT_DIR

# Tentukan port dan environment aktif saat ini berdasarkan file penanda
CURRENT_ENV_FILE="$PROJECT_DIR/.current_env"

if [ ! -f "$CURRENT_ENV_FILE" ]; then
    echo "blue" > "$CURRENT_ENV_FILE"
fi

CURRENT_ENV=$(cat "$CURRENT_ENV_FILE")

if [ "$CURRENT_ENV" == "blue" ]; then
    NEXT_ENV="green"
    NEXT_PORT="3002"
    OLD_PORT="3001"
else
    NEXT_ENV="blue"
    NEXT_PORT="3001"
    OLD_PORT="3002"
fi

echo "--> Deploying to NEXT_ENV: $NEXT_ENV on port $NEXT_PORT (Current: $CURRENT_ENV)"

# Ekspor variabel untuk Docker Compose
export DEPLOY_ENV=$NEXT_ENV
export HOST_PORT=$NEXT_PORT
export DOCKER_IMAGE="my-org/my-app"
export TAG="latest"

# Tarik image terbaru
docker compose pull

# Jalankan kontainer baru
docker compose up -d --no-deps app

# Lakukan Health Check mandiri
echo "--> Performing Health Check on port $NEXT_PORT..."
COUNTER=0
MAX_RETRIES=15

while [ $COUNTER -lt $MAX_RETRIES ]; do
    if curl -s -f "http://localhost:$NEXT_PORT/healthz" > /dev/null; then
        echo "--> Health check PASSED!"
        break
    fi
    COUNTER=$((COUNTER+1))
    echo "Attempt $COUNTER/$MAX_RETRIES: Waiting for application to start..."
    sleep 2
done

if [ $COUNTER -eq $MAX_RETRIES ]; then
    echo "--> Health check FAILED! Rolling back..."
    docker compose stop app_${NEXT_ENV}
    docker compose rm -f app_${NEXT_ENV}
    exit 1
fi

# Perbarui upstream Nginx
echo "upstream backend { server 127.0.0.1:$NEXT_PORT; }" > /etc/nginx/conf.d/upstream.inc

# Reload Nginx secara graceful
sudo nginx -t && sudo nginx -s reload

# Matikan kontainer lama
echo "--> Stopping old environment: app_$CURRENT_ENV"
docker stop app_$CURRENT_ENV || true
docker rm app_$CURRENT_ENV || true

# Simpan state environment terbaru
echo "$NEXT_ENV" > "$CURRENT_ENV_FILE"
echo "--> Deployment completed successfully for $NEXT_ENV!"

Langkah 5: Konfigurasi Nginx Utama (nginx.conf)

Pastikan blok http menyertakan file upstream.inc yang bersifat dinamis.

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Sertakan file upstream dinamis yang diatur oleh skrip deployment
    include /etc/nginx/conf.d/upstream.inc;

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # Timeout tuning untuk koneksi panjang
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }
    }
}

Langkah 6: Pipeline GitHub Actions (.github/workflows/deploy.yml)

name: Production CI/CD

on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: my-org/my-app:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Execute Remote Deployment via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.PRODUCTION_HOST }}
          username: ${{ secrets.PRODUCTION_USER }}
          key: ${{ secrets.PRODUCTION_SSH_KEY }}
          script: |
            cd /var/www/my-project
            git pull origin main
            bash switch-env.sh

6. Bedah Kode & Penjelasan Detail Baris Kunci

Mari kita bedah beberapa bagian paling krusial dari skrip dan konfigurasi di atas:

  1. set -e pada Bash Script: Berfungsi untuk menghentikan eksekusi skrip secara total jika ada perintah tunggal yang mengembalikan nilai keluar (exit code) bukan nol. Ini mencegah skrip melanjutkan proses peralihan upstream jika terjadi kegagalan pada saat penarikan citra atau startup kontainer.
  2. include /etc/nginx/conf.d/upstream.inc;: Pemisahan file upstream ini adalah kunci arsitektur Zero-Downtime. Alih-alih menuliskan blok upstream di dalam file konfigurasi utama server yang memerlukan modifikasi teks besar, kita mengisolasi pointer server ke dalam file terpisah yang ditulis ulang secara atomik oleh skrip switch-env.sh.
  3. proxy_next_upstream error timeout http_502;: (Opsional, dapat ditambahkan di blok location) Memberikan instruksi kepada Nginx untuk otomatis mengalihkan permintaan ke node cadangan jika node utama mengalami kegagalan mendadak di tengah masa transisi.

7. Skenario Produksi & Studi Kasus Dunia Nyata

Menghadapi High Concurrency & Long-Poll / WebSocket

Pada aplikasi real-time yang menggunakan WebSocket atau koneksi HTTP jangka panjang (long-polling), penghentian kontainer lama secara tiba-tiba dapat memutus koneksi klien yang sedang aktif.

Solusi Implementasi:

  1. Tambahkan parameter STOPSIGNAL SIGQUIT di dalam Dockerfile agar kontainer menerima sinyal untuk menghentikan proses secara anggun (graceful shutdown), menyelesaikan permintaan yang sedang berjalan sebelum benar-benar keluar.
  2. Pada skrip deployment, berikan jeda waktu (sleep 10) sebelum kontainer lama dihentikan sepenuhnya, memberikan waktu bagi koneksi yang sedang berjalan untuk selesai secara natural.

8. Common Pitfalls, Anti-Patterns & Solusi

  • Anti-Pattern 1: Migrasi Database Destruktif pada Pipeline Zero-Downtime.

    • Masalah: Melakukan perintah migrasi database yang menghapus kolom atau tabel (DROP COLUMN) secara langsung saat rilis kode baru. Kode versi lama yang masih berjalan di environment seberang akan mengalami crash karena struktur database berubah.
    • Solusi: Terapkan prinsip Expand & Contract Pattern untuk migrasi database. Lakukan penambahan kolom terlebih dahulu (Expand), deploy kode yang kompatibel dengan struktur baru dan lama, lalu hapus kolom lama pada rilis berikutnya (Contract).
  • Anti-Pattern 2: Hardcoding Port di Konfigurasi Nginx.

    • Masalah: Menuliskan proxy_pass http://127.0.0.1:3000; secara langsung di file nginx.conf utama.
    • Solusi: Selalu gunakan konsep dynamic upstream dengan file terpisah atau gunakan service discovery lokal.

9. Benchmark, Metrik Kinerja & Tuning Performa

Untuk memastikan sistem deployment tidak menurunkan performa layanan, lakukan pengujian beban (load testing) menggunakan k6 atau wrk saat proses switch-env sedang berlangsung.

Contoh skrip pengujian beban sederhana menggunakan wrk:

# Menjalankan 100 koneksi paralel selama 30 detik ke endpoint utama
wrk -t12 -c100 -d30s http://example.com/

Hasil Target Metrik Produksi:

  • Error Rate: 0.00% selama proses nginx -s reload dan transisi kontainer.
  • Latency Spike: Lonjakan latensi rata-rata tidak boleh lebih dari 15% dari baseline normal.

10. Checklist Penerapan Produksi & Kesimpulan

Sebelum merilis arsitektur ini ke lingkungan produksi yang sebenarnya, pastikan Anda telah memverifikasi seluruh daftar periksa berikut:

  • Pastikan direktori /var/www/my-project memiliki izin kepemilikan yang benar untuk pengguna eksekusi SSH.
  • Uji skrip switch-env.sh secara manual di staging server minimal 3 kali berturut-turut.
  • Pastikan mekanisme Health Check mengembalikan respons yang akurat (uji skenario ketika database mati).
  • Amankan kredensial Docker Hub dan SSH Private Key di dalam GitHub Actions Secrets.
  • Konfigurasikan rotasi log Docker untuk mencegah penyimpanan log kontainer memenuhi kapasitas disk server (/var/lib/docker).

Dengan mengikuti panduan komprehensif ini, Anda kini memiliki fondasi infrastruktur web enterprise yang tangguh, otomatis, dan sepenuhnya mendukung prinsip Zero-Downtime Deployment.

🏷️ Tags: #Nginx #Devops #Github Actions #Ci/Cd #Docker