Skip to main content

06 — Infrastructure

Dokumen ini menjelaskan strategi deployment, CI/CD, monitoring, dan scaling untuk backend ELJoy.


Environment Strategy​

EnvironmentTujuanInfra
DevelopmentLocal developmentDocker Compose (PG + Redis)
StagingTesting sebelum productionVPS kecil / Railway
ProductionLive untuk usersVPS + 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):

ItemBiaya/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​

MetricToolAlert Threshold
API Response Time (p95)Sentry> 2 detik
Error RateSentry> 1%
Database Query TimePino logs> 500ms
AI Service LatencyPino logs> 5 detik
Redis Memory UsageUpstash dashboard> 80%
Disk UsageVPS monitoring> 85%
Active ConnectionsPM2> 1000

Backup Strategy​

DataFrequencyRetentionMethod
PostgreSQL (full)Setiap hari 02:00 WIB30 hariNeon auto-backup / pg_dump
PostgreSQL (WAL)Continuous7 hariNeon point-in-time recovery
Audio files (R2)ContinuousUnlimitedR2 multi-region replication
RedisTidak di-backup-Data bisa di-rebuild dari PG

Scaling Checklist​

Traffic LevelUsersInfrastruktur
MVP0-5001 VPS + Managed PG + Upstash Redis
Growth500-5K2 VPS (API + Worker) + Neon Pro
Scale5K-50KRailway/Fly.io auto-scale + PG replicas
Enterprise50K+Kubernetes + Cloud SQL + Redis Cluster

Kapan harus scale?

  1. Response time p95 > 2 detik → tambah instance API
  2. Worker queue backlog > 100 jobs → tambah worker
  3. DB connection pool habis → tambah replicas
  4. Redis memory > 80% → upgrade tier