Monitoring & Alerting untuk Next.js di Docker — Setup Lengkap dengan Prometheus, Grafana, dan AlertManager

Update 2026: Artikel ini sudah diuji di VPS Ubuntu 24.04. Cocok untuk developer yang sudah deploy Next.js + Docker sebelumnya.


Pendahuluan

Hai developer! Pernah nggak sih kamu bingung pas aplikasi di production tiba-tiba lambat, tapi kamu nggak tahu kenapa? Atau mungkin dapat notifikasi dari user "kok error ya?" padahal di log kamu nggak ada error apa-apa.

Itu tandanya, kamu butuh monitoring dan alerting.

Di artikel ini, kita bakal bahas cara setup monitoring lengkap untuk Next.js di Docker dengan: - Prometheus — untuk mengumpulkan metrics - Grafana — untuk visualisasi dashboard - AlertManager — untuk alerting - Loki + Promtail — untuk log aggregation - cAdvisor — untuk monitoring container Docker

Kenapa penting? Karena "deploy and forget" itu mahal. Kalau aplikasi crash di production dan kamu baru tahu besok, itu sudah terlambat.


1. Kenapa Monitoring Itu Penting?

Tanpa Monitoring

  • Aplikasi lambat? Kamu baru tahu dari user yang complain

  • Database overload? Kamu baru tahu pas error "database is locked"

  • Memory leak? Server crash tanpa warning

  • Traffic spike? Kamu baru tahu pas server down

Dengan Monitoring

  • Dapat notifikasi sebelum aplikasi crash

  • Bisa lihat pola traffic dan resource usage

  • Bisa analisis slow query

  • Bisa setup auto-scaling berdasarkan metrics

Apa yang Harus Dimonitoring?

  1. Application metrics — response time, error rate, throughput

  2. Container metrics — CPU, memory, disk, network

  3. Database metrics — connection count, query performance

  4. Business metrics — user registrations, API calls, revenue


2. Setup Monitoring Stack di Docker

Kita akan tambahkan service monitoring ke docker-compose.yml. Berikut update-nya:

docker-compose.yml (Monitoring Stack)

version: "3.8"

services:
  # ... service next-app, postgres, caddy dari artikel sebelumnya ...

  # Prometheus — metrics collection
  prometheus:
    image: prom/prometheus:v2.51.0
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./monitoring/prometheus/alerts:/etc/prometheus/alerts
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--storage.tsdb.retention.time=30d'
      - '--web.enable-lifecycle'
    networks:
      - web
    ports:
      - "9090:9090"

  # Grafana — dashboard visualization
  grafana:
    image: grafana/grafana-enterprise:10.2.2
    container_name: grafana
    restart: unless-stopped
    volumes:
      - grafana_data:/var/lib/grafana
      - ./monitoring/grafana/provisioning:/etc/grafana/provisioning
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123
      - GF_USERS_ALLOW_SIGN_UP=false
    networks:
      - web
    ports:
      - "3001:3000"
    depends_on:
      - prometheus

  # AlertManager — alerting
  alertmanager:
    image: prom/alertmanager:v0.27.0
    container_name: alertmanager
    restart: unless-stopped
    volumes:
      - ./monitoring/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
    networks:
      - web
    ports:
      - "9093:9093"

  # cAdvisor — container monitoring
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.48.0
    container_name: cadvisor
    restart: unless-stopped
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    networks:
      - web
    ports:
      - "8080:8080"

  # Loki — log aggregation
  loki:
    image: grafana/loki:2.9.3
    container_name: loki
    restart: unless-stopped
    command: -config.file=/etc/loki/local-config.yaml
    volumes:
      - loki_data:/loki
    networks:
      - web
    ports:
      - "3100:3100"

  # Promtail — log shipping
  promtail:
    image: grafana/promtail:2.9.3
    container_name: promtail
    restart: unless-stopped
    volumes:
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./monitoring/promtail/config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml
    networks:
      - web

networks:
  web:
    external: false

volumes:
  prometheus_data:
  grafana_data:
  loki_data:

Penjelasan: - Prometheus: Mengumpulkan metrics dari berbagai sumber - Grafana: Visualisasi metrics di dashboard yang rapi - AlertManager: Handle alert notifikasi (email, Discord, Slack) - cAdvisor: Monitor Docker container (CPU, memory, network, disk) - Loki + Promtail: Log aggregation — semua log terpusat di satu tempat


3. Konfigurasi Prometheus

Buat file monitoring/prometheus/prometheus.yml:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "/etc/prometheus/alerts/*.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093

scrape_configs:
  # Next.js application metrics
  - job_name: 'next-app'
    static_configs:
      - targets: ['next-app:3000']
    metrics_path: '/api/metrics'

  # cAdvisor — container metrics
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

  # Node Exporter — host metrics
  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  # PostgreSQL metrics
  - job_name: 'postgres'
    static_configs:
      - targets: ['postgres:9187']

  # Prometheus self-monitoring
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  # Loki metrics
  - job_name: 'loki'
    static_configs:
      - targets: ['loki:3100']

Alert Rules

Buat file monitoring/prometheus/alerts/app-alerts.yml:

groups:
  - name: app.alerts
    rules:
      # High error rate
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "High error rate on {{ $labels.instance }}"
          description: "{{ $value }}% of requests are failing"

      # High response time
      - alert: HighResponseTime
        expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 2
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "High response time on {{ $labels.instance }}"
          description: "95th percentile response time is {{ $value }}s"

      # Container high memory
      - alert: ContainerHighMemory
        expr: (container_memory_usage_bytes{container!="POD"} / container_spec_memory_limit_bytes{container!="POD"}) * 100 > 85
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Container {{ $labels.container_name }} high memory"
          description: "Memory usage is {{ $value }}%"

      # Container high CPU
      - alert: ContainerHighCPU
        expr: (rate(container_cpu_usage_seconds_total{container!="POD"}[5m]) * 100) > 80
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Container {{ $labels.container_name }} high CPU"
          description: "CPU usage is {{ $value }}%"

      # Database connection pool exhausted
      - alert: DatabaseConnectionExhausted
        expr: pg_stat_database_numbackends / pg_settings_max_connections * 100 > 80
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Database connection pool exhausted"
          description: "Connection pool usage is {{ $value }}%"

4. Setup AlertManager

Buat file monitoring/alertmanager/alertmanager.yml:

global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.gmail.com:587'
  smtp_from: 'alerts@domainkamu.com'
  smtp_auth_username: 'alerts@domainkamu.com'
  smtp_auth_password: 'app-password-kamu'

route:
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 1h
  receiver: 'discord-notifications'

receivers:
  - name: 'discord-notifications'
    discord_configs:
      - api_url: 'https://discord.com/api/webhooks/webhook-id/webhook-token'
        channel: 'alerts'

  - name: 'email-notifications'
    email_configs:
      - to: 'dev-team@domainkamu.com'
        send_resolved: true

  - name: 'slack-notifications'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/webhook-url'
        channel: '#alerts'
        send_resolved: true

inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'instance']

Catatan: Untuk Discord webhook, buat dulu di Server Settings → Integrations → Webhooks. Untuk email, pakai app password khusus (bukan password biasa).


5. Monitoring Next.js Application Metrics

Health Check Endpoint

Buat endpoint health check yang juga expose metrics:

// app/api/health/route.ts
import { NextResponse } from 'next/server'
import { register, Counter, Gauge, Histogram } from 'prom-client'

// Custom metrics
const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
})

const httpRequestTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code'],
})

const activeConnections = new Gauge({
  name: 'active_connections',
  help: 'Number of active connections',
})

export async function GET() {
  return NextResponse.json({ 
    status: 'ok', 
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
  })
}

// Metrics endpoint
export async function POST(request: Request) {
  // Record metrics
  const startTime = Date.now()
  const url = new URL(request.url)

  try {
    // Your API logic here
    const duration = (Date.now() - startTime) / 1000

    httpRequestDuration.observe({
      method: request.method,
      route: url.pathname,
      status_code: '200',
    }, duration)

    httpRequestTotal.inc({
      method: request.method,
      route: url.pathname,
      status_code: '200',
    })

    return NextResponse.json({ success: true })
  } catch (error) {
    httpRequestDuration.observe({
      method: request.method,
      route: url.pathname,
      status_code: '500',
    }, (Date.now() - startTime) / 1000)

    httpRequestTotal.inc({
      method: request.method,
      route: url.pathname,
      status_code: '500',
    })

    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
  }
}

// Metrics endpoint for Prometheus
export async function PUT() {
  return new NextResponse(await register.metrics(), {
    headers: { 'Content-Type': register.contentType },
  })
}

OpenTelemetry Integration

Untuk monitoring yang lebih advance, gunakan OpenTelemetry:

# Install dependencies
npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
npm install @opentelemetry/exporter-prometheus
npm install @opentelemetry/exporter-trace-otlp-http

Buat file instrumentation.ts:

import { register } from 'node:module'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace'

const traceExporter = new OTLPTraceExporter({
  url: 'http://localhost:4318/v1/traces',
})

const sdk = new NodeSDK({
  traceExporter,
  instrumentations: [getNodeAutoInstrumentations()],
})

sdk.start()

6. Grafana Dashboard Setup

Provisioning Dashboard

Buat file monitoring/grafana/provisioning/datasources/datasource.yml:

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    url: http://prometheus:9090
    access: proxy
    isDefault: true

  - name: Loki
    type: loki
    url: http://loki:3100
    access: proxy

Dashboard JSON (Simplified)

Buat file monitoring/grafana/provisioning/dashboards/nextjs-dashboard.json:

{
  "dashboard": {
    "id": null,
    "title": "Next.js Application Monitoring",
    "tags": ["nextjs", "docker"],
    "timezone": "browser",
    "panels": [
      {
        "type": "graph",
        "title": "HTTP Request Rate",
        "datasource": "Prometheus",
        "targets": [
          {
            "expr": "rate(http_requests_total[5m])",
            "legendFormat": "{{method}} {{status_code}}"
          }
        ]
      },
      {
        "type": "graph",
        "title": "Response Time (95th percentile)",
        "datasource": "Prometheus",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "95th percentile"
          }
        ]
      },
      {
        "type": "stat",
        "title": "Active Connections",
        "datasource": "Prometheus",
        "targets": [
          {
            "expr": "active_connections",
            "legendFormat": "Active"
          }
        ]
      },
      {
        "type": "graph",
        "title": "Container Memory Usage",
        "datasource": "Prometheus",
        "targets": [
          {
            "expr": "container_memory_usage_bytes{container!=\"POD\"}",
            "legendFormat": "{{container_name}}"
          }
        ]
      },
      {
        "type": "graph",
        "title": "Container CPU Usage",
        "datasource": "Prometheus",
        "targets": [
          {
            "expr": "rate(container_cpu_usage_seconds_total{container!=\"POD\"}[5m]) * 100",
            "legendFormat": "{{container_name}}"
          }
        ]
      }
    ]
  }
}

7. Log Aggregation dengan Loki

Promtail Config

Buat file monitoring/promtail/config.yml:

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: system
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          host: localhost
          agent: promtail
          __path__: /var/log/*.log

  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: [__meta_docker_container_name]
        regex: "/(.*)"
        target_label: container
      - source_labels: [__meta_docker_container_id]
        target_label: container_id
      - source_labels: [__meta_docker_container_name]
        target_label: job

Query Logs di Grafana

Setelah Loki terhubung, kamu bisa query logs di Grafana:

# Error logs dari Next.js
{container="next-app"} |= "error" |~ ".*"

# Request logs
{container="next-app"} |~ "GET|POST|PUT|DELETE" | pattern `<timestamp> <level> <message>`

# Slow query logs
{container="postgres"} |= "duration:" | logfmt | duration > 1000

8. Post-Setup Checklist

Setelah monitoring terpasang, jalankan checklist ini:

✅ Verifikasi Semua Service Berjalan

  • [ ] Prometheus accessible di http://domain:9090

  • [ ] Grafana accessible di http://domain:3001 (login: admin/admin123)

  • [ ] AlertManager accessible di http://domain:9093

  • [ ] cAdvisor accessible di http://domain:8080

  • [ ] Loki accessible di http://domain:3100

✅ Dashboard & Metrics

  • [ ] Import dashboard Next.js di Grafana

  • [ ] Pastikan metrics terkumpulkan (cek Prometheus targets)

  • [ ] Test alert dengan trigger manual

  • [ ] Setup notifikasi (Discord/Slack/email)

✅ Alert Rules

  • [ ] High error rate (>5%)

  • [ ] High response time (>2s)

  • [ ] Container high memory (>85%)

  • [ ] Container high CPU (>80%)

  • [ ] Database connection pool exhausted (>80%)

✅ Log Aggregation

  • [ ] Semua container log terlihat di Grafana Loki

  • [ ] Query error logs berfungsi

  • [ ] Alert berdasarkan log pola

✅ Security

  • [ ] Ganti password Grafana default

  • [ ] Setup HTTPS untuk semua service monitoring

  • [ ] Restrict access ke monitoring stack (hanya dari IP tertentu)

  • [ ] Setup authentication untuk Prometheus dan AlertManager


9. Troubleshooting

Error: "Connection refused" ke Prometheus

# Cek Prometheus sudah berjalan
docker-compose ps

# Cek log
docker-compose logs prometheus

# Cek config file
docker-compose exec prometheus cat /etc/prometheus/prometheus.yml

Error: "No targets" di Prometheus

# Cek target yang terdaftar
curl http://localhost:9090/api/v1/targets

# Pastikan service yang dimonitor sudah berjalan
docker-compose ps

# Cek network connectivity
docker-compose exec prometheus ping next-app

Error: Alert tidak terkirim

# Cek AlertManager log
docker-compose logs alertmanager

# Test webhook manual
curl -X POST -d '[{"receiver":"discord-notifications","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"TestAlert"}}]}]' http://localhost:9093/api/v1/alerts

Error: Loki tidak menerima log

# Cek Promtail log
docker-compose logs promtail

# Cek konfigurasi
docker-compose exec promtail cat /etc/promtail/config.yml

# Test push log manual
curl -X POST -H "Content-Type: application/json" -d '{"streams":[{"stream":{"job":"test"},"values":[["'$(date +%s)000000000'", "test log message"]]}]}' http://localhost:3100/loki/api/v1/push

10. Kesimpulan

Nah itu dia panduan lengkap setup monitoring dan alerting untuk Next.js di Docker. Simpel kan?

Kesimpulan utama: 1. Monitoring wajib untuk production — jangan deploy and forget 2. Prometheus + Grafana adalah kombinasi yang solid untuk metrics 3. AlertManager untuk notifikasi yang terpusat 4. Loki + Promtail untuk log aggregation 5. cAdvisor untuk monitoring Docker container

Best practices: - Setup alert sebelum masuk production - Gunakan dashboard yang informatif (jangan berlebihan) - Test alert secara berkala - Backup konfigurasi monitoring - Monitor resource usage dan optimalkan

Stack yang direkomendasikan: - Metrics: Prometheus + Grafana - Alerting: AlertManager (Discord/Slack webhook) - Logging: Loki + Promtail + Grafana - Container: cAdvisor - Tracing: OpenTelemetry (untuk advanced use case)

Kalau kamu punya pertanyaan tentang monitoring, jangan ragu tinggalkan komentar. Semoga artikel ini membantu kamu deploy aplikasi yang production-ready!


FAQ

Q: Berapa resource yang butuh monitoring stack ini? A: Sekitar 500MB-1GB RAM tergantung traffic. Untuk VPS kecil (1GB), kurangi retention time Prometheus ke 7 hari.

Q: Boleh nggak pakai layanan monitoring managed (seperti Grafana Cloud)? A: Boleh banget! Justru lebih direkomendasikan untuk production. Tinggal ganti URL datasource ke endpoint yang diberikan.

Q: Bagaimana cara backup konfigurasi monitoring? A: Backup folder monitoring/ ke git. Konfigurasi Prometheus, Grafana, AlertManager, dan Loki semuanya ada di sana.

Q: Perlu nggak setup SSL untuk service monitoring? A: Wajib untuk production. Gunakan Caddy sebagai reverse proxy untuk semua service monitoring, atau restrict akses via IP.


Artikel ini ditulis oleh developer Indonesia, untuk developer Indonesia. Kalau berguna, jangan lupa share ke teman-teman!

Artikel ini ditulis oleh developer Indonesia, untuk developer Indonesia.