feat: add comprehensive deployment guide for Docker and GitHub Actions
This commit is contained in:
@@ -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 (<VPS_HOST>)
|
||||
docker compose pull + up
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Engine >= 24.x
|
||||
- Docker Compose v2 (plugin)
|
||||
- Git
|
||||
- Akun GitHub dengan akses repo
|
||||
- SSH key di `~/.ssh/<KEY_NAME>` (default: `id_ed25519`)
|
||||
|
||||
## Konfigurasi VPS Target
|
||||
|
||||
Buat berkas `~/orangevps` (atau sesuaikan dengan env Anda):
|
||||
|
||||
```text
|
||||
ssh <USER>@<VPS_HOST>
|
||||
```
|
||||
|
||||
Contoh isi `~/orangevps`:
|
||||
|
||||
```text
|
||||
ssh root@45.127.35.244
|
||||
```
|
||||
|
||||
| Parameter | Nilai | Contoh |
|
||||
|-----------|-------|--------|
|
||||
| User | `<USER>` | `root` |
|
||||
| Host | `<VPS_HOST>` | `45.127.35.244` |
|
||||
| SSH Key | `~/.ssh/<KEY_NAME>` | `~/.ssh/id_ed25519` |
|
||||
| Target Dir di VPS | `<VPS_TARGET_DIR>` | `/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 | `<account>.dkr.ecr.<region>.amazonaws.com` | `aws ecr get-login-password` |
|
||||
| Google GCR | `gcr.io` | `gcloud auth print-access-token` |
|
||||
| Azure ACR | `<registry>.azurecr.io` | `az acr login` |
|
||||
|
||||
Contoh namespace untuk GHCR:
|
||||
|
||||
```text
|
||||
Registry : ghcr.io
|
||||
Namespace: <GITHUB_USERNAME_OR_ORG>
|
||||
Repo : <REPO_NAME>
|
||||
```
|
||||
|
||||
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 }}/<SERVICE_NAME>:latest
|
||||
ghcr.io/${{ github.repository }}/<SERVICE_NAME>:sha-${{ github.sha }}
|
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository }}/<SERVICE_NAME>:buildcache
|
||||
cache-to: type=registry,ref=ghcr.io/${{ github.repository }}/<SERVICE_NAME>:buildcache,mode=max
|
||||
```
|
||||
|
||||
Ubah `<SERVICE_NAME>` 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/<GITHUB_USER>/<REPO_NAME>.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/<KEY_NAME>` |
|
||||
| `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 <GITHUB_USERNAME> --password-stdin
|
||||
```
|
||||
|
||||
Build dan push:
|
||||
|
||||
```bash
|
||||
docker build -t ghcr.io/<GITHUB_USERNAME>/<REPO_NAME>/<SERVICE_NAME>:latest -f Dockerfile .
|
||||
|
||||
docker push ghcr.io/<GITHUB_USERNAME>/<REPO_NAME>/<SERVICE_NAME>:latest
|
||||
```
|
||||
|
||||
Tag tambahan dengan SHA commit:
|
||||
|
||||
```bash
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
docker tag ghcr.io/<GITHUB_USERNAME>/<REPO_NAME>/<SERVICE_NAME>:latest \
|
||||
ghcr.io/<GITHUB_USERNAME>/<REPO_NAME>/<SERVICE_NAME>:sha-${SHORT_SHA}
|
||||
docker push ghcr.io/<GITHUB_USERNAME>/<REPO_NAME>/<SERVICE_NAME>:sha-${SHORT_SHA}
|
||||
```
|
||||
|
||||
### 2. Pull dan Deploy di VPS
|
||||
|
||||
SSH ke VPS:
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/<KEY_NAME> <USER>@<VPS_HOST>
|
||||
```
|
||||
|
||||
Clone repo (jika belum):
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<GITHUB_USER>/<REPO_NAME>.git <VPS_TARGET_DIR>
|
||||
cd <VPS_TARGET_DIR>
|
||||
```
|
||||
|
||||
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 <GITHUB_USERNAME> --password-stdin
|
||||
```
|
||||
|
||||
Pull gambar terbaru:
|
||||
|
||||
```bash
|
||||
cd <VPS_TARGET_DIR>
|
||||
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 <SERVICE_NAME>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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 <container_name>
|
||||
|
||||
# Restart satu service
|
||||
docker compose -f <compose_file> 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 <GITHUB_USERNAME> --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 <VPS_TARGET_DIR>
|
||||
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
|
||||
Reference in New Issue
Block a user