From 44a56fa0a9d28525ce1e857a7ad40f444c001421 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Tue, 21 Jul 2026 08:37:49 +0700 Subject: [PATCH] feat: add comprehensive deployment guide for Docker and GitHub Actions --- docs/DEPLOYMENT.md | 614 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 614 insertions(+) create mode 100644 docs/DEPLOYMENT.md diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..057527e --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,614 @@ +# 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