# Devops

> Skill untuk deployment, CI/CD, Docker, dan infrastructure. TRIGGER ketika: user minta deploy ke Vercel/Netlify/VPS, setup Docker/docker-compose, buat CI/CD pipeline (GitHub Actions, GitLab CI), konfigurasi Nginx/reverse proxy, setup SSL, monitoring, atau environment management. Juga trigger saat: Dockerfile, docker-compose, .github/workflows, atau deployment error.

- Skill: `edorabma9304/devops` (Agent Skill)
- Install (CLI): `npx skillmds@latest add edorabma9304/devops`
- Raw SKILL.md: https://api.skillmd.com/api/skills/edorabma9304/devops/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: edorabma9304 (https://skillmd.com/u/edorabma9304)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/edorabma9304/devops

---


# DevOps & Deployment Skill

Skill universal untuk deployment, containerization, CI/CD, dan infrastructure management.

---

## Deployment Platforms

### Vercel (Recommended for React/Next.js)

#### Setup
```bash
npm i -g vercel
vercel login
vercel              # deploy preview
vercel --prod       # deploy production
```

#### vercel.json
```json
{
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "framework": "vite",
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ],
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
    }
  ]
}
```

#### Environment Variables
```bash
vercel env add VITE_SUPABASE_URL        # per environment
vercel env add VITE_SUPABASE_ANON_KEY
```

### Netlify

#### netlify.toml
```toml
[build]
  command = "npm run build"
  publish = "dist"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"
```

### VPS (Ubuntu/Debian)

#### Setup Lengkap
```bash
# 1. Install Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# 2. Install PM2
sudo npm install -g pm2

# 3. Clone & build
git clone <repo-url> /var/www/app
cd /var/www/app
npm install
npm run build

# 4. Serve with PM2 (untuk SSR/API)
pm2 start npm --name "app" -- start
pm2 save
pm2 startup

# 5. Atau serve static files dengan Nginx (untuk SPA)
sudo apt install -y nginx
```

#### Nginx Config (SPA)
```nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/app/dist;
    index index.html;

    # SPA fallback
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache static assets
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
```

#### SSL dengan Certbot
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# Auto-renew via cron sudah otomatis
```

---

## Docker

### Dockerfile (Node.js App)
```dockerfile
# Build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```

### Dockerfile (API/Backend)
```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
USER node
CMD ["node", "server.js"]
```

### docker-compose.yml (Full Stack)
```yaml
version: '3.8'

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "80:80"
    depends_on:
      - api
    restart: unless-stopped

  api:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/appdb
      - NODE_ENV=production
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=appdb
    restart: unless-stopped

volumes:
  pgdata:
```

### .dockerignore
```
node_modules
dist
.git
.env
.env.local
*.md
.DS_Store
```

---

## CI/CD

### GitHub Actions — Build & Deploy
```yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - run: npm ci
      - run: npm run build
      - run: npm test --if-present

      # Deploy ke Vercel
      - uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'
```

### GitHub Actions — PR Check
```yaml
# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main, dev]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - run: npm test --if-present
      - run: npx eslint . --if-present
```

### GitLab CI
```yaml
# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

build:
  stage: build
  image: node:20-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths: [dist/]

test:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm test

deploy:
  stage: deploy
  only: [main]
  script:
    - npx vercel --prod --token=$VERCEL_TOKEN
```

---

## Environment Management

### Struktur File
```
.env                # Default/shared (commit ke git HANYA jika non-sensitif)
.env.local          # Local overrides (JANGAN commit)
.env.development    # Dev-specific
.env.production     # Production-specific
```

### Naming Convention (Vite)
```bash
# Prefix VITE_ agar terekspos ke frontend
VITE_API_URL=https://api.example.com
VITE_SUPABASE_URL=https://xxx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJ...

# Tanpa prefix = hanya server-side
DATABASE_URL=postgresql://...
JWT_SECRET=xxx
```

### .gitignore
```
.env.local
.env.*.local
.env.production
```

---

## Monitoring & Health Check

### PM2 Monitoring
```bash
pm2 monit           # Real-time monitoring
pm2 logs app        # View logs
pm2 status          # List all processes
```

### Health Check Endpoint (Express)
```javascript
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    uptime: process.uptime(),
    timestamp: new Date().toISOString(),
    version: process.env.npm_package_version,
  })
})
```

### Uptime Monitoring
- UptimeRobot (gratis 50 monitors)
- Better Stack (gratis 10 monitors)
- Cek: response time, status code, SSL expiry

---

## Aturan DevOps

- Jangan hardcode secrets — selalu pakai environment variables
- Jangan commit `.env.local` atau file berisi credentials
- Build harus reproducible — pin dependency versions (`npm ci`, bukan `npm install`)
- Setiap deploy harus melalui CI pipeline — jangan deploy manual ke production
- Rollback plan harus ada sebelum deploy
- Zero-downtime deployment jika memungkinkan
- Log semua deployment (siapa, kapan, versi apa)

