# Deployment Guide Panduan deploy aplikasi apapun menggunakan **Docker + Docker Compose + GitHub Actions + VPS**. ## Arsitektur ``` GitHub Repo ──► GitHub Actions ──► Registry (GHCR / Docker Hub / ECR / dll.) │ ▼ VPS () docker compose pull + up ``` ## Prerequisites - Docker Engine >= 24.x - Docker Compose v2 (plugin) - Git - Akun GitHub dengan akses repo - SSH key di `~/.ssh/` (default: `id_ed25519`) ## Konfigurasi VPS Target Buat berkas `~/orangevps` (atau sesuaikan dengan env Anda): ```text ssh @ ``` Contoh isi `~/orangevps`: ```text ssh root@45.127.35.244 ``` | Parameter | Nilai | Contoh | |-----------|-------|--------| | User | `` | `root` | | Host | `` | `45.127.35.244` | | SSH Key | `~/.ssh/` | `~/.ssh/id_ed25519` | | Target Dir di VPS | `` | `/opt/app` atau `/root/app` | > Tip: Jika SSH key menggunakan nama selain default, sesuaikan path dan `ssh -i` sesuai. ## Registry Pilih registry untuk menyimpan image Docker. Sesuaikan dengan proyek: | Registry | URL | Auth | |----------|-----|------| | GitHub Container Registry | `ghcr.io` | `GITHUB_TOKEN` | | Docker Hub | `docker.io` | username / PAT | | AWS ECR | `.dkr.ecr..amazonaws.com` | `aws ecr get-login-password` | | Google GCR | `gcr.io` | `gcloud auth print-access-token` | | Azure ACR | `.azurecr.io` | `az acr login` | Contoh namespace untuk GHCR: ```text Registry : ghcr.io Namespace: Repo : ``` Pastikan package/visibility di registry mengizinkan akses pull dari VPS. --- ## Deploy Otomatis (Recommended) Gunakan GitHub Actions untuk otomatisasi build, push, dan deploy. ### Workflow 1: Build dan Push Image File: `.github/workflows/docker-build-push.yml` ```yaml name: Build and Push Docker Images on: push: branches: [main] workflow_dispatch: permissions: contents: read packages: write jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/setup-buildx-action@v4 - uses: docker/build-push-action@v7 with: context: . file: Dockerfile push: true tags: | ghcr.io/${{ github.repository }}/:latest ghcr.io/${{ github.repository }}/:sha-${{ github.sha }} cache-from: type=registry,ref=ghcr.io/${{ github.repository }}/:buildcache cache-to: type=registry,ref=ghcr.io/${{ github.repository }}/:buildcache,mode=max ``` Ubah `` sesuai service (misal: `app`, `web`, `api`). Jika monorepo, gunakan matrix strategy untuk build beberapa service sekaligus. ### Workflow 2: Deploy ke VPS File: `.github/workflows/deploy-docker.yml` ```yaml name: Deploy Docker to VPS on: workflow_run: workflows: ['Build and Push Docker Images'] types: [completed] push: branches: [main] workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Deploy to VPS env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} VPS_HOST: ${{ secrets.VPS_HOST }} VPS_USER: ${{ secrets.VPS_USER }} VPS_TARGET_DIR: ${{ secrets.VPS_TARGET_DIR }} ENV_FILE_PRODUCTION: ${{ secrets.ENV_FILE_PRODUCTION }} run: | set -euo pipefail mkdir -p ~/.ssh echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -H -t ed25519,rsa "$VPS_HOST" >> ~/.ssh/known_hosts SSH_OPTS=(-o ControlMaster=auto -o ControlPath=/tmp/ssh-%r@%h:%p -o ControlPersist=600 -o StrictHostKeyChecking=yes) ssh "${SSH_OPTS[@]}" "$VPS_USER@$VPS_HOST" "mkdir -p $VPS_TARGET_DIR && mkdir -p $VPS_TARGET_DIR/infra/compose" echo "$ENV_FILE_PRODUCTION" > .env.prod scp "${SSH_OPTS[@]}" .env.prod "$VPS_USER@$VPS_HOST:$VPS_TARGET_DIR/.env" ssh "${SSH_OPTS[@]}" "$VPS_USER@$VPS_HOST" bash -s <<'EOF' set -euo pipefail cd "$VPS_TARGET_DIR" docker network inspect app-shared-net >/dev/null 2>&1 || docker network create app-shared-net if [ ! -d ".git" ]; then git init git remote add origin https://github.com//.git fi git fetch origin main --depth=1 || true git reset --hard FETCH_HEAD docker compose --env-file .env pull docker compose --env-file .env up -d --remove-orphans EOF ``` ### Secrets GitHub yang Diperlukan Buka **Settings > Secrets and variables > Actions**: | Secret | Deskripsi | |--------|-----------| | `SSH_PRIVATE_KEY` | Isi dengan `cat ~/.ssh/` | | `VPS_HOST` | IP atau domain VPS | | `VPS_USER` | User SSH (misal: `root`, `ubuntu`, `deploy`) | | `VPS_TARGET_DIR` | Direktori aplikasi di VPS | | `ENV_FILE_PRODUCTION` | Isi dengan environment production | ### Trigger Manual ```bash gh workflow run deploy-docker.yml ``` --- ## Dockerfile Patterns Pilih pattern sesuai jenis aplikasi. ### Pattern 1: Multi-stage Build (SPA / static assets) ```dockerfile FROM oven/bun:1 AS builder WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ### Pattern 2: Single-stage (runtime image) ```dockerfile FROM oven/bun:1 WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . EXPOSE 3000 CMD ["bun", "run", "start"] ``` ### Pattern 3: Compiled binary (Rust / Go / Zig) ```dockerfile FROM rust:1 AS builder WORKDIR /app COPY . . RUN cargo build --release FROM debian:bookworm-slim COPY --from=builder /app/target/release/app /usr/local/bin/app EXPOSE 8080 CMD ["app"] ``` --- ## Docker Compose Patterns ### Single service ```yaml services: app: container_name: app image: registry.example.com/org/app:latest restart: always ports: - "3000:3000" environment: - NODE_ENV=production ``` ### Multi-service dengan shared network ```yaml services: app: container_name: app image: registry.example.com/org/app:latest restart: always networks: [app-shared-net] redis: container_name: redis image: redis:7-alpine restart: always networks: [app-shared-net] networks: app-shared-net: name: app-shared-net external: true ``` ### Dengan reverse proxy (Traefik / Caddy / Nginx) ```yaml services: app: container_name: app image: registry.example.com/org/app:latest restart: always networks: [app-shared-net] labels: - 'traefik.enable=true' - 'traefik.http.routers.app.rule=Host(`app.example.com`)' - 'traefik.http.routers.app.entrypoints=websecure' - 'traefik.http.routers.app.tls=true' - 'traefik.http.services.app.loadbalancer.server.port=3000' networks: app-shared-net: name: app-shared-net external: true ``` --- ## Deploy Manual (Lokal) ### 1. Build dan Push ke Registry Login ke registry: ```bash echo $GITHUB_TOKEN | docker login ghcr.io -u --password-stdin ``` Build dan push: ```bash docker build -t ghcr.io///:latest -f Dockerfile . docker push ghcr.io///:latest ``` Tag tambahan dengan SHA commit: ```bash SHORT_SHA=$(git rev-parse --short HEAD) docker tag ghcr.io///:latest \ ghcr.io///:sha-${SHORT_SHA} docker push ghcr.io///:sha-${SHORT_SHA} ``` ### 2. Pull dan Deploy di VPS SSH ke VPS: ```bash ssh -i ~/.ssh/ @ ``` Clone repo (jika belum): ```bash git clone https://github.com//.git cd ``` Buat shared network (hanya sekali): ```bash docker network create app-shared-net ``` Siapkan environment: ```bash cp .env.example .env # Edit .env sesuai nilai production nano .env ``` Login ke registry di VPS: ```bash echo $GITHUB_TOKEN | docker login ghcr.io -u --password-stdin ``` Pull gambar terbaru: ```bash cd docker compose -f docker-compose.yml --env-file .env pull ``` Deploy (up): ```bash docker compose -f docker-compose.yml --env-file .env up -d --remove-orphans ``` Verifikasi: ```bash docker compose -f docker-compose.yml ps docker compose -f docker-compose.yml logs -f ``` --- ## Deployment Order (Manual) Jika deploy bertahap, gunakan urutan ini: ```bash # 1. Shared services (Redis, database, dll.) docker compose -f infra/compose/shared.yml up -d # 2. Reverse proxy docker compose -f infra/compose/traefik.yml up -d # 3. Aplikasi docker compose \ -f infra/compose/app1.yml \ -f infra/compose/app2.yml \ up -d ``` --- ## Perintah Berguna di VPS ```bash # Lihat semua container docker ps -a # Log service docker logs -f # Restart satu service docker compose -f up -d --force-recreate # Hapus network lama (hati-hati) docker network rm app-shared-net docker network create app-shared-net # Bersihkan image unused docker image prune -a -f docker system prune -a -f ``` --- ## Troubleshooting ### Image tidak bisa di-pull Pastikan sudah login ke registry di VPS: ```bash docker logout ghcr.io echo $GITHUB_TOKEN | docker login ghcr.io -u --password-stdin ``` Periksa visibility package di registry (harus `Public` atau akses diberikan). ### Port sudah dipakai ```bash docker ps | grep :80 docker ps | grep :443 ``` ### Reverse proxy tidak routing Periksa label di compose file dan pastikan shared network ada: ```bash docker network inspect app-shared-net docker logs traefik ``` --- ## Environment Variable Management ### Pola 1: `.env` di VPS (recommended untuk production) ```bash # Di VPS cd cp .env.example .env # Edit sesuai production nano .env ``` CI/CD upload `.env` via secret, tidak simpan di repo. ### Pola 2: Docker secrets (Swarm mode) ```yaml services: app: image: app:latest secrets: - db_password secrets: db_password: file: ./secrets/db_password.txt ``` ### Pola 3: External secret manager - **HashiCorp Vault**: inject via env atau file - **AWS Secrets Manager**: `aws secretsmanager get-secret-value` - **Doppler / Infisical**: unified secret management --- ## Tagging dan Versioning ### Strategy yang umum | Strategy | Contoh tag | Kegunaan | |----------|-----------|----------| | Latest + SHA | `latest`, `sha-abc1234` | CI/CD cepat, traceable | | SemVer | `1.2.3`, `1.2`, `1` | Release publik | | Git tag mirror | `v1.2.3` | Sync dengan git tag | | Branch mirror | `main`, `develop` | Preview / staging | ### Contoh git tag driven deploy ```bash git tag v1.2.3 git push origin v1.2.3 ``` CI/CD membaca tag, build image dengan tag yang sama, dan deploy. --- ## Rollback ### Rollback via registry ```bash # Lihat tag yang tersedia docker manifest inspect ghcr.io/org/app:latest # atau lihat UI registry # Di VPS, edit compose file ke tag sebelumnya # lalu: docker compose --env-file .env pull docker compose --env-file .env up -d --remove-orphans ``` ### Rollback via git ```bash git revert HEAD git push origin main # CI/CD otomatis build dan deploy versi sebelumnya ``` --- ## Health Checks ### Di Dockerfile ```dockerfile HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1 ``` ### Di Docker Compose ```yaml services: app: image: app:latest healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 3s retries: 3 start_period: 10s ``` --- ## Monitoring & Observability ```bash # Log aggregated docker compose logs -f --tail=100 # Resource usage docker stats # Disk usage docker system df # Cleanup docker system prune -a -f ``` --- ## Catatan Keamanan - Jangan commit `.env` atau SSH private key ke repo. - Gunakan GitHub Secrets (atau secret manager) untuk credential di CI/CD. - Rotate token dan key secara berkala. - Batasi akses SSH ke VPS (ubah port default, gunakan fail2ban). - Set `StrictHostKeyChecking=yes` pada SSH opsional deployment. --- ## Checklist Deploy Proyek Baru 1. [ ] Dockerfile ditest lokal (`docker build`, `docker run`) 2. [ ] Docker Compose file valid (`docker compose config`) 3. [ ] `.dockerignore` sesuai (node_modules, .git, .env) 4. [ ] Registry dibuat (GHCR package / Docker Hub repo / ECR / dll.) 5. [ ] GitHub Actions workflow dibuat dengan permission `packages: write` 6. [ ] VPS siap: Docker, Docker Compose, SSH key 7. [ ] Shared network dibuat (`docker network create`) 8. [ ] `.env` production di-VPS atau via secret manager 9. [ ] Reverse proxy (Traefik / Caddy / Nginx) routing ke container 10. [ ] Health check endpoint aktif