06 — Infrastructure
Dokumen ini menjelaskan strategi deployment, CI/CD, monitoring, dan scaling untuk backend ELJoy.
Environment Strategy
| Environment | Tujuan | Infra |
|---|---|---|
| Development | Local development | Docker Compose (PG + Redis) |
| Staging | Testing sebelum production | VPS kecil / Railway |
| Production | Live untuk users | VPS + Managed DB |
Deployment Architecture
Opsi 1: VPS (Rekomendasi Awal)
Paling sederhana dan cost-effective untuk MVP dan awal:
┌─────────────────────────────────────────────────┐
│ Cloudflare │
│ (DNS + CDN + WAF + DDoS) │
└──────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ VPS (Hetzner / DigitalOcean) │
│ 4 vCPU, 8GB RAM, 80GB SSD │
│ ~$15-20/bulan │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Node.js │ │ Node.js │ (PM2) │
│ │ API Server │ │ Worker │ │
│ │ (Port 3001) │ │ (BullMQ) │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ Caddy │ (Reverse Proxy + Auto SSL) │
│ │ (Port 443) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Neon / Supabase│ │ Upstash Redis │
│ (Managed PG) │ │ (Serverless) │
│ Free tier │ │ Free tier │
└──────────────┘ └──────────────┘
Estimasi Biaya Bulanan (MVP):
| Item | Biaya/Bulan |
|---|---|
| VPS (Hetzner CX22) | ~$5 |
| Neon PostgreSQL (Free tier → Pro) | $0 - $19 |
| Upstash Redis (Free tier) | $0 |
| Cloudflare (Free plan) | $0 |
| Cloudflare R2 (10GB free) | $0 |
| Domain (.id) | ~$2 |
| Total MVP | ~$7 - $26/bulan |
Opsi 2: Serverless (Skala Menengah)
Untuk traffic lebih tinggi, migrasi ke serverless:
Cloudflare → Railway / Fly.io → Neon PG + Upstash Redis
Opsi 3: Container Orchestration (Skala Besar)
Untuk ribuan concurrent users:
Cloudflare → Kubernetes (GKE/EKS) → Cloud SQL + Redis Cluster
CI/CD Pipeline
GitHub Actions Workflow
# .github/workflows/deploy-backend.yml
name: Deploy Backend
on:
push:
branches: [main]
paths: ['apps/app-be/**']
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: eljoy_test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports: ['5432:5432']
redis:
image: redis:7
ports: ['6379:6379']
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm --filter app-be run typecheck
- run: pnpm --filter app-be run lint
- run: pnpm --filter app-be run test
env:
DATABASE_URL: postgresql://test:test@localhost:5432/eljoy_test
REDIS_URL: redis://localhost:6379
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to VPS
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /app/eljoy-apps
git pull origin main
pnpm install --frozen-lockfile
pnpm --filter app-be run build
pnpm --filter app-be run db:migrate
pm2 restart eljoy-api
pm2 restart eljoy-worker
Pipeline Stages
Push ke main
│
▼
┌──────────┐
│ Typecheck │ → tsc --noEmit
└────┬─────┘
▼
┌──────────┐
│ Lint │ → biome check
└────┬─────┘
▼
┌──────────┐
│ Test │ → vitest run (unit + integration)
└────┬─────┘
▼
┌──────────┐
│ Build │ → tsc (compile to JS)
└────┬─────┘
▼
┌──────────┐
│ Migrate │ → drizzle-kit migrate (di production DB)
└────┬─────┘
▼
┌──────────┐
│ Deploy │ → PM2 restart (zero-downtime)
└──────────┘
Docker Compose (Local Development)
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
ports:
- '5432:5432'
environment:
POSTGRES_DB: eljoy
POSTGRES_USER: eljoy
POSTGRES_PASSWORD: eljoy_dev
volumes:
- pg_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- '6379:6379'
# Opsional: pgAdmin untuk visual DB management
pgadmin:
image: dpage/pgadmin4
ports:
- '5050:80'
environment:
PGADMIN_DEFAULT_EMAIL: admin@eljoy.id
PGADMIN_DEFAULT_PASSWORD: admin
volumes:
pg_data:
# Jalankan services
docker compose up -d
# Jalankan API server (development mode)
pnpm --filter app-be run dev
# Jalankan BullMQ worker
pnpm --filter app-be run worker:dev
Monitoring & Observability
Logging (Pino + Better Stack)
import pino from 'pino';
const logger = pino({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
transport: process.env.NODE_ENV === 'production'
? {
target: '@logtail/pino',
options: { sourceToken: process.env.LOGTAIL_SOURCE_TOKEN },
}
: {
target: 'pino-pretty', // Pretty print di development
},
});
// Usage
logger.info({ userId, action: 'assessment.completed' }, 'User completed assessment');
logger.error({ error, requestId }, 'AI service failed');
Error Tracking (Sentry)
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1, // 10% of transactions
});
// Auto-capture errors from Hono
app.onError((err, c) => {
Sentry.captureException(err, {
extra: {
path: c.req.path,
method: c.req.method,
userId: c.get('userId'),
},
});
// ... return error response
});
Health Check
// GET /health — untuk uptime monitoring
app.get('/health', async (c) => {
const checks = {
server: 'ok',
database: 'checking...',
redis: 'checking...',
};
try {
await db.execute(sql`SELECT 1`);
checks.database = 'ok';
} catch {
checks.database = 'error';
}
try {
await redis.ping();
checks.redis = 'ok';
} catch {
checks.redis = 'error';
}
const allOk = Object.values(checks).every(v => v === 'ok');
return c.json(checks, allOk ? 200 : 503);
});
Key Metrics to Monitor
| Metric | Tool | Alert Threshold |
|---|---|---|
| API Response Time (p95) | Sentry | > 2 detik |
| Error Rate | Sentry | > 1% |
| Database Query Time | Pino logs | > 500ms |
| AI Service Latency | Pino logs | > 5 detik |
| Redis Memory Usage | Upstash dashboard | > 80% |
| Disk Usage | VPS monitoring | > 85% |
| Active Connections | PM2 | > 1000 |
Backup Strategy
| Data | Frequency | Retention | Method |
|---|---|---|---|
| PostgreSQL (full) | Setiap hari 02:00 WIB | 30 hari | Neon auto-backup / pg_dump |
| PostgreSQL (WAL) | Continuous | 7 hari | Neon point-in-time recovery |
| Audio files (R2) | Continuous | Unlimited | R2 multi-region replication |
| Redis | Tidak di-backup | - | Data bisa di-rebuild dari PG |
Scaling Checklist
| Traffic Level | Users | Infrastruktur |
|---|---|---|
| MVP | 0-500 | 1 VPS + Managed PG + Upstash Redis |
| Growth | 500-5K | 2 VPS (API + Worker) + Neon Pro |
| Scale | 5K-50K | Railway/Fly.io auto-scale + PG replicas |
| Enterprise | 50K+ | Kubernetes + Cloud SQL + Redis Cluster |
Kapan harus scale?
- Response time p95 > 2 detik → tambah instance API
- Worker queue backlog > 100 jobs → tambah worker
- DB connection pool habis → tambah replicas
- Redis memory > 80% → upgrade tier