Overviewh2
Panduan ini mencakup setup VPS yang bisa dipakai untuk hosting banyak aplikasi sekaligus. Arsitekturnya dirancang scalable agar aplikasi berdiri sendiri, dan Caddy berperan sebagai reverse proxy terpusat yang menangani semua traffic masuk.
Setup ini sebenarnya bukan murni dari nol yang saya rancang sendiri tapi berdasar pengalaman waktu kerja ngefreelance, saya belajar pola ini dari senior engineer saya di tim yang sudah dulu membuat infrastrukturnya. Lalu saya mencoba pahami cara kerjanya, tiru alurnya, lalu tulis ulang di sini sebagai catatan pribadi biar bisa dipakai lagi ke depannya.
Arsitekturh3
┌───────────────┐ │ Caddy │← satu2nya yang pegang port 80/443 │ (port 80/443) │ └──────┬────────┘ │ ┌────────────┼────────────┐ │ │ │ ┌─────▼────┐ ┌─────▼────┐ ┌─────▼────┐ │ app-1 │ │ app-2 │ │ app-3 │ │ │ │ (nanti) │ │ (nanti) │ └──────────┘ └──────────┘ └──────────┘Semua container terhubung lewat shared Docker network, sehingga Caddy bisa reverse proxy ke aplikasi.
Kenapa Caddy Dipisah dari Aplikasi?h3
Kalau Caddy menempel di tiap aplikasi, setiap aplikasi akan rebutan port 80/443 karena hanya boleh ada satu proses yang listen di port tersebut dalam satu VPS.
Dengan Caddy terpisah sebagai gerbang utama:
- hanya Caddy yang expose port 80/443 ke publik
- restart/update satu aplikasi tidak mengganggu aplikasi lain
- buat nambah aplikasi tinggal buat folder + compose baru
- Caddy yang ngurus Let’s Encrypt sendiri per domain/subdomain
Struktur Folder di VPSh3
~/├── caddy/│ ├── docker-compose.yml│ └── Caddyfile│├── app-1/│ ├── docker-compose.yml│ └── .env│└── app-2/ (nanti, kalau ada aplikasi baru) ├── docker-compose.yml └── .envPersiapan Awal VPSh2
Install Dockerh3
curl -fsSL https://get.docker.com | shsudo systemctl enable dockersudo systemctl start dockerIzinkan User Menjalankan Docker Tanpa sudoh3
sudo usermod -aG docker $USERnewgrp dockerVerifikasi:
docker psBuat Shared Networkh3
Network ini jadi jembatan yang menghubungkan Caddy dengan semua aplikasi.
docker network create proxy_netSetup Caddy (Reverse Proxy Terpusat)h2
mkdir -p ~/caddycd ~/caddydocker-compose.ymlh3
services: caddy: image: caddy:2-alpine container_name: caddy ports: - '80:80' - '443:443' volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config networks: - proxy_net restart: unless-stopped
networks: proxy_net: external: true
volumes: caddy_data: caddy_config:Caddyfile (sementara, sebelum punya domain)h3
Kalau belum punya domain, bisa gunain IP langsung. tapi caddy gak akan generate SSL karena Let’s Encrypt butuh domain valid.
:80 { handle /api/* { reverse_proxy app-be:3000 } handle { reverse_proxy app-fe:80 }}Caddyfile (setelah punya domain)h3
Ganti dengan nama subdomain. Caddy otomatis generate SSL dari Let’s Encrypt.
myapp.domainmu.com { handle /api/* { reverse_proxy app-be:3000 } handle { reverse_proxy app-fe:80 }}Untuk menambah aplikasi baru, cukup tambahkan blok baru:
app2.domainmu.com { reverse_proxy app2:PORT}Jalankan Caddyh3
docker compose up -ddocker compose psReload Caddy Setelah Update Caddyfileh3
docker compose exec caddy caddy reload --config /etc/caddy/CaddyfileTidak perlu restart container Caddy bisa reload konfigurasi secara live tanpa downtime.
Setup Aplikasih2
Setiap aplikasi punya folder dan docker-compose.yml sendiri. Berikut contoh untuk aplikasi dengan stack frontend (React/Vue) + backend (Go/Node) + database (PostgreSQL).
mkdir -p ~/my-appcd ~/my-appdocker-compose.ymlh3
services: postgres: image: postgres:17-alpine restart: unless-stopped environment: POSTGRES_USER: ${DB_USER:-postgres} POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME}'] interval: 10s timeout: 5s retries: 5 networks: - proxy_net
app-be: image: ${DOCKER_USERNAME}/my-app-be:${BE_TAG:-latest} container_name: app-be environment: DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASSWORD}@postgres:5432/${DB_NAME}?sslmode=disable PORT: ${BE_PORT:-3000} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS} JWT_SECRET: ${JWT_SECRET} depends_on: postgres: condition: service_healthy restart: unless-stopped networks: - proxy_net
app-fe: image: ${DOCKER_USERNAME}/my-app-fe:${FE_TAG:-latest} container_name: app-fe restart: unless-stopped networks: - proxy_net
networks: proxy_net: external: true
volumes: postgres_data:Catatan penting:
- Tidak ada service
caddydi sini kan sudah dipisah ke~/caddy/ - Semua service yang perlu diakses Caddy wajib join
networks: - proxy_net container_namedi set eksplisit agar bisa dipakai sebagai hostname di Caddyfile
Jalankan Aplikasih3
docker compose up -ddocker compose psdocker compose logs -fMenambah Aplikasi Baruh2
Karena arsitekturnya sudah scalable, buat nambah aplikasi baru cukup:
- Buat folder baru
~/app-2/dengandocker-compose.ymltrus pastiin semua service yang perlu publik joinnetworks: - proxy_netdenganexternal: true - Tambahkan blok baru di
~/caddy/Caddyfile:
app2.domainmu.com { reverse_proxy app2-service:PORT}- Reload Caddy:
cd ~/caddydocker compose exec caddy caddy reload --config /etc/caddy/CaddyfileTidak perlu restart Caddy atau aplikasi lain yang sudah berjalan.