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?
Application metrics — response time, error rate, throughput
Container metrics — CPU, memory, disk, network
Database metrics — connection count, query performance
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.



