PM2 vs Systemd vs Docker untuk VPS 2026
Tiga cara jalankan Node/Bun app di VPS production. Saya pakai PM2 36 bulan, Systemd 24 bulan, Docker 30 bulan. Verdict tergantung tim + complexity stack.
TL;DR
- PM2: Node-native, cluster mode + monitoring UI. Conditional Recommend untuk Node solo app.
- Systemd: OS-level, lightweight, robust. Conditional Recommend untuk lightweight + production-grade.
- Docker: Isolation, portable, multi-service. Conditional Recommend untuk team / multi-service.
- Verdict: pilih berdasarkan stack size + team size + portability need.
Konteks
Saya pakai ketiga di 12 VPS production SMB Indonesia 2022-2026:
- PM2 (5 VPS): klien Node app simple (Hono backend mobile, Next.js standalone build), klien Bun app via PM2 v6
- Systemd (4 VPS): klien minimal stack, VPS RAM kecil (1-2GB), klien Bun app native
- Docker (3 VPS): klien multi-service (app + worker + Redis + Postgres), klien yang move ke containerization
Plus klien K8s di Railway / fly.io (managed Docker — di luar scope artikel ini).
Pricing
Ketiga gratis. Cost = resource overhead + waktu setup + maintenance.
Resource overhead
| Tool | RAM overhead | CPU overhead | Disk |
|---|---|---|---|
| PM2 | 30-60 MB per app | minimal | 20 MB install |
| Systemd | < 5 MB per service | minimal | bawaan OS |
| Docker | 50-150 MB per container + 100-200 MB daemon | 2-5% baseline | 200-500 MB install |
Systemd paling efisien. Docker paling heavy tapi paling isolated.
VPS recommendation per tool
| Tool | Minimum VPS |
|---|---|
| PM2 (1 Node app) | 1GB RAM |
| Systemd (1 Node/Bun app) | 512MB RAM |
| Docker (1 container) | 2GB RAM |
| Docker Compose (3-5 service) | 4GB RAM |
Feature comparison
| Feature | PM2 | Systemd | Docker |
|---|---|---|---|
| Restart on crash | Yes | Yes | Yes (with restart policy) |
| Start on boot | Yes (pm2 startup) | Yes (enable) | Yes (restart: always) |
| Cluster mode | Yes (native) | Manual (multiple service) | Yes (Compose scale) |
| Log management | Yes (pm2 logs + rotate) | journalctl | Yes (docker logs) |
| Monitoring | Yes (pm2 monit + plus dashboard) | systemd-cgtop | Yes (docker stats) |
| Zero-downtime reload | Yes (pm2 reload) | Yes (with socket activation) | Yes (Compose rolling) |
| Environment isolation | No | No | Yes (full) |
| Portability | Node-specific | OS-specific (systemd) | Cross-platform |
| Config syntax | ecosystem.config.js | INI-like .service | Dockerfile + Compose YAML |
| Learning curve | Low (Node dev familiar) | Medium (Linux admin) | Medium-high |
Trade-off
PM2
Pro: Node-native — Node dev familiar tanpa learning curve. Cluster mode bawaan (utilize multi-core). PM2 Plus dashboard untuk monitoring (paid). Hot-reload + zero-downtime reload built-in. Log rotation handle automatic dengan pm2-logrotate.
Con: PM2 daemon overhead 30-60MB per app. Tidak isolation antara app. Update PM2 sometimes break compat (lock version). Bukan ideal untuk non-Node runtime.
Systemd
Pro: OS-native — bawaan Ubuntu/Debian/CentOS, no install extra. Paling lightweight (< 5MB overhead). Restart policy granular (on-failure, on-abnormal, always). Socket activation untuk lazy start. Default modern Linux distro.
Con: Syntax INI awkward untuk dev non-Linux-admin. Log via journalctl (learning curve dari tail -f). No native cluster — butuh multiple service file untuk cluster mode. Less monitoring UI (tooling external).
Docker
Pro: Isolation full (filesystem, network, process). Portable — same image jalan dev, staging, prod. Multi-service via Compose. Ekosistem matang (Hub, Compose, Swarm, K8s). Reproducible build via Dockerfile.
Con: Overhead RAM + CPU + disk paling besar. Learning curve untuk dev Indonesia yang tidak familiar containerization. Image size kalau tidak optimize bisa balloon (1-3GB). Setup awal lebih kompleks (Dockerfile + Compose).
Config comparison
PM2 (Node app)
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-api',
script: './dist/server.js',
instances: 'max', // cluster mode
exec_mode: 'cluster',
env: { NODE_ENV: 'production', PORT: 3000 },
max_memory_restart: '500M',
error_file: './logs/error.log',
out_file: './logs/out.log',
}]
};
pm2 start ecosystem.config.js
pm2 save
pm2 startup
Systemd (Bun app)
# /etc/systemd/system/my-api.service
[Unit]
Description=My API
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/my-api
ExecStart=/usr/local/bin/bun run server.ts
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now my-api
sudo systemctl status my-api
journalctl -u my-api -f
Docker Compose (full stack)
# docker-compose.yml
services:
api:
build: .
restart: always
ports: ["3000:3000"]
environment:
NODE_ENV: production
DATABASE_URL: postgres://db:5432/myapp
depends_on: [db, redis]
worker:
build: .
command: bun run worker.ts
restart: always
environment:
DATABASE_URL: postgres://db:5432/myapp
REDIS_URL: redis://redis:6379
db:
image: postgres:17
restart: always
volumes: ["pgdata:/var/lib/postgresql/data"]
environment:
POSTGRES_PASSWORD: secret
redis:
image: redis:7
restart: always
volumes: ["redisdata:/data"]
volumes:
pgdata:
redisdata:
docker compose up -d
docker compose logs -f api
docker compose restart api # zero downtime
Performance benchmark
VPS Biznet 4GB RAM, Bun + Hono app.
Idle memory consumption
| Tool | Total RAM used |
|---|---|
| PM2 + Node app | 95 MB |
| Systemd + Bun app | 38 MB |
| Docker + Bun app | 145 MB |
| Docker Compose (app + db + redis) | 480 MB |
Cold start (after VPS reboot)
| Tool | Time to first response |
|---|---|
| PM2 | 3-5s |
| Systemd | 1-3s |
| Docker (already pulled) | 4-8s |
| Docker Compose (full stack) | 12-20s |
Systemd fastest cold start.
Restart time (after crash)
| Tool | Time to recover |
|---|---|
| PM2 | 1-3s |
| Systemd (Restart=on-failure) | 5-10s (default RestartSec) |
| Docker (restart: always) | 3-6s |
PM2 fastest auto-restart.
Konteks Indonesia
Klien mobile backend Jakarta (PM2 + Node + VPS Biznet Rp 250rb/bulan): 1 API service, cluster mode untuk 4 vCPU. PM2 logs + monit untuk troubleshoot. Maintenance 15-30 menit/bulan. Cocok untuk Node dev solo.
Klien content site Tangerang (Systemd + Bun + VPS Niagahoster Rp 80rb/bulan): 1GB RAM VPS. Bun runtime native — Systemd overhead minimal. Setup 20 menit. Maintenance < 10 menit/bulan.
Klien HR SaaS Jakarta (Docker Compose + VPS Biznet 4GB RAM Rp 450rb/bulan): app + queue worker + Redis + Postgres. Docker Compose orchestrate. Deploy via git pull + docker compose up. Setup awal 4 jam (familiar Docker). Maintenance 30-45 menit/bulan.
Use case decision matrix
| Scenario | Recommended |
|---|---|
| Solo Node app, VPS 1-2GB RAM | PM2 atau Systemd |
| Bun app native | Systemd |
| Multi-service (app + db + cache) | Docker Compose |
| VPS RAM kecil (< 1GB) | Systemd |
| Dev team familiar Docker | Docker |
| Need portability dev/staging/prod | Docker |
| Cluster mode Node | PM2 (cluster native) atau Docker Swarm |
| Production-grade restart policy | Systemd atau Docker |
| Monitoring dashboard | PM2 Plus atau Docker + Portainer |
Migration paths
PM2 → Systemd
Effort: 30-60 menit per app. Write .service file, disable PM2, enable systemd. Worth jika RAM tight atau Bun-first stack.
PM2/Systemd → Docker
Effort: 2-6 jam (write Dockerfile + Compose). Worth jika add multi-service atau move ke containerization team-wide.
Docker → PM2/Systemd
Rare. Effort: 1-3 jam. Strip container, run app native via PM2/Systemd. Worth jika simplify untuk solo dev atau cost VPS reduce.
Gotcha (lesson learned)
1. PM2 startup script
pm2 startup generate command — harus run sebagai sudo. Lupa = PM2 tidak auto-start setelah reboot. Pernah hit ini di 2 klien.
2. Systemd Restart=always vs on-failure
Restart=always: restart even after clean exit. Cocok untuk daemon.Restart=on-failure: hanya restart jika exit code != 0. Cocok untuk app dengan graceful shutdown.
Pilih based on app behavior. Salah pilih = restart loop atau missed restart.
3. Docker image size
Naive Dockerfile bisa balloon ke 1-3GB. Optimization:
- Multi-stage build (build di stage 1, copy artifact ke stage 2)
- Pakai base image slim (node:alpine, oven/bun:alpine)
- .dockerignore untuk exclude node_modules, .git
Target: < 200MB per image untuk app standard.
4. Log rotation
PM2: install pm2-logrotate module. Default tidak rotate — disk full risk.
Systemd: journalctl auto-rotate by size + time.
Docker: configure log-driver: json-file dengan max-size: 10m, max-file: 3.
5. Health check
- PM2:
--max-restarts+ custom health endpoint check via cron. - Systemd:
WatchdogSec+Type=notify(advanced). - Docker:
HEALTHCHECKinstruction di Dockerfile.
Wajib untuk production — restart automatic kalau app hang.
Verdict
Conditional Recommend untuk ketiga:
Pakai PM2 jika:
- Node app solo (1-3 service)
- Tim familiar PM2 workflow
- Butuh cluster mode + monitoring dashboard
- VPS 1-2GB RAM acceptable
Pakai Systemd jika:
- VPS RAM tight (< 1GB)
- Bun-native runtime
- Prefer OS-level standard
- Tim comfort dengan Linux admin
Pakai Docker jika:
- Multi-service (app + db + cache + worker)
- Team-wide containerization
- Portability dev/staging/prod
- VPS 4GB+ RAM
Skip jika: Anda pakai PaaS (Vercel, Railway, Fly.io) — process management sudah handled.
Untuk reverse proxy companion: nginx vs Caddy vs Traefik. Untuk admin panel Laravel: Bundle Admin Panel Laravel + Livewire.
Ditulis oleh Asti Larasati