← Semua picks

Stack Comparison Conditional

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.

22 Juni 2026 · 9 menit ·Use case: Process manager Node/Bun app di VPS SMB Indonesia
PM2SystemdDocker

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

ToolRAM overheadCPU overheadDisk
PM230-60 MB per appminimal20 MB install
Systemd< 5 MB per serviceminimalbawaan OS
Docker50-150 MB per container + 100-200 MB daemon2-5% baseline200-500 MB install

Systemd paling efisien. Docker paling heavy tapi paling isolated.

VPS recommendation per tool

ToolMinimum 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

FeaturePM2SystemdDocker
Restart on crashYesYesYes (with restart policy)
Start on bootYes (pm2 startup)Yes (enable)Yes (restart: always)
Cluster modeYes (native)Manual (multiple service)Yes (Compose scale)
Log managementYes (pm2 logs + rotate)journalctlYes (docker logs)
MonitoringYes (pm2 monit + plus dashboard)systemd-cgtopYes (docker stats)
Zero-downtime reloadYes (pm2 reload)Yes (with socket activation)Yes (Compose rolling)
Environment isolationNoNoYes (full)
PortabilityNode-specificOS-specific (systemd)Cross-platform
Config syntaxecosystem.config.jsINI-like .serviceDockerfile + Compose YAML
Learning curveLow (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

ToolTotal RAM used
PM2 + Node app95 MB
Systemd + Bun app38 MB
Docker + Bun app145 MB
Docker Compose (app + db + redis)480 MB

Cold start (after VPS reboot)

ToolTime to first response
PM23-5s
Systemd1-3s
Docker (already pulled)4-8s
Docker Compose (full stack)12-20s

Systemd fastest cold start.

Restart time (after crash)

ToolTime to recover
PM21-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

ScenarioRecommended
Solo Node app, VPS 1-2GB RAMPM2 atau Systemd
Bun app nativeSystemd
Multi-service (app + db + cache)Docker Compose
VPS RAM kecil (< 1GB)Systemd
Dev team familiar DockerDocker
Need portability dev/staging/prodDocker
Cluster mode NodePM2 (cluster native) atau Docker Swarm
Production-grade restart policySystemd atau Docker
Monitoring dashboardPM2 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: HEALTHCHECK instruction 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

// Pick Stack Comparison lain


← Semua picks RSS feed