Pendahuluan: Dari Kontainer Lokal Menuju Lingkungan Produksi Nyata
Pada Part 2 dari series ini, kita telah membentengi platform pendamping open-source kita—mfarim/spring-elearning-react—dari celah keamanan path traversal, memasang rotasi token refresh otomatis, dan menambahkan probe kesehatan Spring Boot Actuator.
Kini saatnya melangkah ke tahap operasional sesungguhnya: menerbitkan sistem ke server publik nyata.
Bagi institusi sekolah mandiri, universitas, atau startup edukasi, menyewa klaster Kubernetes atau managed cloud multi-instance (seharga $150–$300/bulan) sering kali tidak realistis dan membebani anggaran. Sebaliknya, satu server Single Virtual Private Server (VPS) seharga $6–$15/bulan sanggup melayani ribuan peserta ujian secara stabil jika dirancang dengan isolasi kontainer, konfigurasi reverse proxy yang tepat, dan alokasi memori swap yang memadai.
Panduan ini mendokumentasikan blueprint deployment langkah demi langkah yang telah teruji di lapangan untuk menerbitkan Spring E-Learning & CBT pada subdomain produksi:
👉 https://spring-elearning.mfarim.com
1. Arsitektur Deployment Single VPS
Pada single VPS, Nginx yang terpasang di Host VPS bertugas mengelola port 80 (HTTP) dan 443 (HTTPS) serta menghentikan SSL (SSL Termination). Nginx Host kemudian meneruskan traffic secara aman ke kontainer Docker di internal localhost pada port loopback khusus (127.0.0.1:8088):
Keuntungan Pola Arsitektur Ini:
- Koeksistensi dengan Aplikasi Lain: Port 80 dan 443 tetap dipegang oleh Host Nginx, memungkinkan server yang sama menjalankan portofolio/blog utama (
mfarim.com) tanpa bentrok port. - Keamanan Database Terisolasi: PostgreSQL dan Redis tidak dibuka ke internet publik (
0.0.0.0), terlindung dari serangan brute force credential. - Persistensi Data Mutlak: Seluruh materi tugas, PDF silabus, dan data tabel tersimpan di named volume Docker (
postgres_data,redis_data,backend_storage).
2. Spesifikasi Server & Persiapan Sistem
Spesifikasi Minimum yang Direkomendasikan:
- OS: Ubuntu 22.04 LTS atau 24.04 LTS (64-bit)
- CPU: Minimal 2 vCPU
- RAM: Minimal 2 GB (Wajib dialokasikan Swap Memory 2–4 GB agar kompilasi Java Maven dan Vite tidak terkena terminasi Linux OOM Killer). Rekomendasi ideal: 4 GB RAM.
- Storage: Minimal 25 GB SSD/NVMe
Langkah 1: Pembaruan Sistem & Alokasi Swap Memory
Masuk ke VPS via SSH:
BASH
ssh root@<IP_PUBLIC_VPS>
Perbarui paket repositori Ubuntu:
BASH
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git ufw htop ca-certificates gnupg lsb-release
Alokasi Swap Memory (Wajib untuk RAM 2 GB):
Kompilasi TypeScript di Vite dan startup JVM Java 21 membutuhkan lonjakan memori sementara. Buat Swap 4 GB:
BASH
# Periksa apakah swap sudah aktif
sudo swapon --show
# Buat file swap 4 GB
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Jadikan swap permanen setelah restart server
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Optimasi nilai swappiness
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
Konfigurasi Firewall (UFW):
Amankan VPS dengan membuka hanya port penting:
BASH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
3. Konfigurasi DNS Subdomain
Sebelum memasang sertifikat SSL, arahkan subdomain ke IP Address VPS pada panel DNS penyedia domain Anda:
| Tipe Record | Nama Host / Subdomain | Target / Points To | TTL | Catatan |
|---|---|---|---|---|
| A | spring-elearning | <IP_PUBLIC_VPS> | 300 / Auto | Jika memakai Cloudflare, matikan Proxy (DNS Only / Grey Cloud) saat pertama kali validasi SSL. |
Verifikasi propagasi DNS:
BASH
dig +short spring-elearning.mfarim.com
# Harus memunculkan IP Public VPS Anda
4. Instalasi Docker, Nginx Host & Certbot
Install Docker Engine & Docker Compose via Repository Resmi:
BASH
# Hapus paket lama jika ada
sudo apt remove -y docker docker-engine docker.io containerd runc
# Pasang keyring resmi Docker
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Tambahkan repository Docker
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker CE dan Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Aktifkan service Docker
sudo systemctl enable docker
sudo systemctl start docker
# Verifikasi
docker compose version
Install Nginx Host & Certbot:
BASH
sudo apt install -y nginx certbot python3-certbot-nginx
sudo systemctl enable nginx
sudo systemctl start nginx
5. Setup Repositori & Environment Produksi (.env)
Kloning repositori ke direktori standar server /var/www/spring-elearning:
BASH
sudo mkdir -p /var/www/spring-elearning
sudo chown -R $USER:$USER /var/www/spring-elearning
git clone https://github.com/mfarim/spring-elearning-react.git /var/www/spring-elearning
cd /var/www/spring-elearning
Buat File .env Produksi:
BASH
cp .env.example .env
nano .env
Sesuaikan variabel dengan kredensial kuat:
INI
# ==============================================================================
# Variabel Environment Produksi
# ==============================================================================
# --- Kredensial Database ---
POSTGRES_DB=elearning
POSTGRES_USER=elearning_prod_user
# Buat password acak yang kuat (misal via: openssl rand -hex 16)
POSTGRES_PASSWORD=Secur3_P0stgr3s_Pr0d_K3y_2026!
# --- JWT Signing Key (Wajib string Base64 256-bit!) ---
# Generate via: openssl rand -base64 32
JWT_SECRET=WjgrSmZkN1Fsa1ZReTh4V2tQd3JzZXQ4N2FscTNyd2Vxdzh5aGphaXN1Zz0=
# --- Path Penyimpanan di Dalam Kontainer ---
STORAGE_UPLOAD_DIR=/app/storage/uploads
# --- Port Forwarding Internal ke Host Nginx ---
FRONTEND_PORT=8088
BACKEND_PORT=8080
[!IMPORTANT] Cara Menghasilkan Kunci JWT Secret yang Sah: Jalankan
openssl rand -base64 32di terminal dan salin string hasilnya keJWT_SECRET=di file.env.
6. Menjalankan Docker Compose Produksi
Gunakan konfigurasi docker-compose.prod.yml yang mengarahkan frontend ke loopback port 127.0.0.1:8088:
BASH
docker compose -f docker-compose.prod.yml up -d --build
Memeriksa Status Kontainer:
BASH
docker compose -f docker-compose.prod.yml ps
Seluruh kontainer harus berstatus healthy / up:
elearning-postgres(healthy)elearning-redis(healthy)elearning-backend(healthy)elearning-frontend(up, terikat ke127.0.0.1:8088->80/tcp)
Memeriksa Log Startup Backend:
BASH
docker compose -f docker-compose.prod.yml logs -f backend
Pastikan log menampilkan:
TEXT
Tomcat started on port 8080 (http) with context path '/'
Started BackendApplication in 8.412 seconds
Verifikasi Health Check Internal:
BASH
curl -s http://127.0.0.1:8088/actuator/health
# Respons: {"status":"UP"}
7. Konfigurasi Virtual Host Nginx Host
Sekarang atur Nginx di Host VPS agar meneruskan traffic dari subdomain spring-elearning.mfarim.com ke port internal 127.0.0.1:8088 dengan dukungan unggah file 50MB dan WebSocket persistent.
Buat File Konfigurasi Nginx:
BASH
sudo nano /etc/nginx/sites-available/spring-elearning.mfarim.com
Isi konfigurasi berikut:
NGINX
server {
server_name spring-elearning.mfarim.com;
# Izinkan unggah modul pembelajaran hingga 50MB
client_max_body_size 50M;
access_log /var/log/nginx/spring-elearning_access.log;
error_log /var/log/nginx/spring-elearning_error.log;
# Frontend SPA & REST API
location / {
proxy_pass http://127.0.0.1:8088;
proxy_set_header Host $host;
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;
}
# WebSocket STOMP untuk Telemetri Ujian CBT Real-Time
location /ws {
proxy_pass http://127.0.0.1:8088/ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
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;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
listen 80;
}
Aktifkan Konfigurasi & Uji Sintaks:
BASH
sudo ln -s /etc/nginx/sites-available/spring-elearning.mfarim.com /etc/nginx/sites-enabled/
sudo nginx -t
# Jika sintaks valid, reload Nginx:
sudo systemctl reload nginx
8. Pemasangan Sertifikat SSL (HTTPS) dengan Certbot
Pasang sertifikat TLS/SSL gratis dengan auto-redirect HTTP ke HTTPS:
BASH
sudo certbot --nginx -d spring-elearning.mfarim.com
Certbot akan menanyakan alamat email dan persetujuan Terms of Service. Setelah itu, Certbot akan memvalidasi domain via ACME challenge dan memperbarui konfigurasi Nginx dengan port 443 SSL.
Uji Perpanjangan Otomatis SSL:
BASH
sudo certbot renew --dry-run
Output: Congratulations, all simulated renewals succeeded.
9. Checklist Verifikasi Fitur Produksi
Buka browser Anda dan akses: 👉 https://spring-elearning.mfarim.com
Jalankan pengujian 5 langkah berikut:
| Parameter Pengujian | Aksi | Hasil yang Diharapkan |
|---|---|---|
| 1. Gembok SSL | Periksa bilah alamat browser | Sertifikat TLS valid terenkripsi oleh Let's Encrypt |
| 2. Login Super Admin | Masuk dengan [email protected] / password | Masuk ke dashboard admin dengan ringkasan statistik |
| 3. Refresh Token | Periksa Network Tab saat sesi aktif | Background endpoint /api/v1/auth/refresh menjaga sesi |
| 4. Unggah File | Unggah modul materi berukuran 15MB | Berkas terunggah sukses tanpa error HTTP 413 |
| 5. Live WebSocket | Buka menu Monitoring Ujian CBT (/exams/:id/monitor) | Konsol browser menampilkan status STOMP connected |
10. Script Redeployment Otomatis (deploy.sh)
Untuk mempermudah pembaruan kode di kemudian hari, buat script deploy di /var/www/spring-elearning/deploy.sh:
BASH
cat << 'EOF' > /var/www/spring-elearning/deploy.sh
#!/bin/bash
set -e
echo "🚀 [1/4] Menarik perubahan kode terbaru dari GitHub..."
cd /var/www/spring-elearning
git pull origin main
echo "📦 [2/4] Melakukan build dan restart kontainer..."
docker compose -f docker-compose.prod.yml build
docker compose -f docker-compose.prod.yml up -d --remove-orphans
echo "🧹 [3/4] Membersihkan Docker image lama yang tidak terpakai..."
docker image prune -f
echo "✅ [4/4] Memeriksa status kesehatan kontainer..."
sleep 10
docker compose -f docker-compose.prod.yml ps
echo "🎉 Deployment berhasil dan aplikasi berjalan lancar!"
EOF
chmod +x /var/www/spring-elearning/deploy.sh
Kapan saja Anda ingin deploy update, cukup jalankan:
BASH
./deploy.sh
11. Backup Database Harian Otomatis
Cegah kehilangan nilai ujian dan bank soal dengan cronjob backup database berkala.
Buat Script Backup:
BASH
sudo mkdir -p /var/backups/elearning
cat << 'EOF' > /var/backups/elearning/backup-db.sh
#!/bin/bash
BACKUP_DIR="/var/backups/elearning"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
FILENAME="$BACKUP_DIR/elearning_db_$TIMESTAMP.sql.gz"
# Dump database PostgreSQL langsung dari dalam kontainer dan kompres
docker exec elearning-postgres pg_dump -U postgres elearning | gzip > "$FILENAME"
# Hapus backup yang lebih lama dari 14 hari
find "$BACKUP_DIR" -type f -name "*.sql.gz" -mtime +14 -delete
echo "Backup database selesai: $FILENAME"
EOF
chmod +x /var/backups/elearning/backup-db.sh
Pasang Cronjob (Jalan Otomatis Pukul 02:00 Pagi Setiap Hari):
BASH
sudo crontab -e
Tambahkan:
CRON
0 2 * * * /var/backups/elearning/backup-db.sh >> /var/log/elearning_backup.log 2>&1
12. Troubleshooting Masalah Populer di Lingkungan Produksi
1. Bind for 0.0.0.0:80 failed: port is already allocated
- Penyebab: Nginx Host sudah menggunakan port 80.
- Solusi: Di
docker-compose.prod.yml, kontainer frontend dipetakan ke127.0.0.1:8088:80, bukan port 80 host. Pastikan Anda menjalankan compose dengan-f docker-compose.prod.yml.
2. 413 Request Entity Too Large saat Mengunggah Materi
- Penyebab: Batas body default Nginx adalah 1MB.
- Solusi: Pastikan
client_max_body_size 50M;terpasang di kedua tempat: Nginx Host (/etc/nginx/sites-available/...) dan Nginx Kontainer (frontend/nginx.conf), lalu reload Nginx host:sudo systemctl reload nginx.
3. Kontainer Backend Mati dengan Status Exit 137 (OOM Killed)
- Penyebab: Memori RAM habis sehingga kernel Linux mematikan proses JVM.
- Solusi:
- Pastikan Swap Memory minimal 2 GB–4 GB aktif (
swapon --show). - Batasi konsumsi heap JVM di
backend/Dockerfiledengan opsi-Xms256m -Xmx512mpadaENTRYPOINT.
- Pastikan Swap Memory minimal 2 GB–4 GB aktif (
4. WebSocket Terputus / Gagal Handshake pada Ujian CBT
- Penyebab: Nginx Host tidak mem-forward header
UpgradedanConnection. - Solusi: Pastikan blok
location /wspada konfigurasi Nginx memuatproxy_set_header Upgrade $http_upgrade;danproxy_read_timeout 86400s;.
5. Memuat Data Demo / Uji Coba Soal & Akun
Untuk memuat akun guru, siswa, dan bank soal contoh dari seed_demo_data.sql:
BASH
docker exec -i elearning-postgres psql -U postgres -d elearning < /var/www/spring-elearning/backend/src/main/resources/db/migration/seed_demo_data.sql
Kesimpulan & Langkah Selanjutnya
Menerbitkan aplikasi skala enterprise tidak harus mahal. Dengan memadukan isolasi kontainer Docker Compose, reverse proxy Nginx Host dengan SSL Certbot, tuning memori Swap, dan backup otomatis, aplikasi Java 21 Spring Boot 3 & React 19 E-Learning dapat berjalan kokoh, aman, dan hemat biaya pada satu VPS.
Pada Part 4, kita akan membedah Desain Model Domain & Skema Flyway PostgreSQL, menelusuri bagaimana relasi database dirancang untuk keandalan data jangka panjang.
- URL Produksi: spring-elearning.mfarim.com
- Repositori Open Source: github.com/mfarim/spring-elearning-react

