Productivity

Prometheus Grafana Docker Compose OOM Fix on 2GB RAM VPS

Prometheus Grafana Docker Compose OOM Fix on 2GB RAM VPS

The OOM killer fired at 3:47 AM. Grafana was gone. Prometheus held on for another six minutes, then it too vanished. The Docker Compose stack — the one managing production monitoring — had eaten itself alive on a $6/month VPS with 2GB RAM.

Not a hypothetical. A pattern dozens of engineers hit every week when they drop a standard Prometheus + Grafana Docker Compose configuration onto a budget VPS and walk away. The default configs assume you have resources to burn. A 2GB RAM VPS does not.

With VPS costs staying flat in 2026 and more teams running self-hosted monitoring stacks to dodge Grafana Cloud’s per-metric billing, getting this configuration right matters. The difference between a stack that runs for months and one that OOM crashes at 3 AM comes down to about six specific settings.

Key Takeaways

  • Default Prometheus + Grafana Docker Compose configurations can consume 1.4–1.8GB RAM at idle on a 2GB VPS, leaving insufficient headroom for the Linux kernel and other processes.
  • Docker memory limits (mem_limit) combined with Prometheus --storage.tsdb.retention.size and chunk encoding settings are the three highest-impact configuration changes for memory-constrained environments.
  • Grafana’s analytics reporting and plugin auto-update features each add 50–120MB of background memory overhead that’s straightforward to disable.
  • A properly tuned Prometheus + Grafana Docker Compose stack on a 2GB RAM VPS can hold steady below 900MB combined RSS, leaving a safe buffer against OOM crashes.

Why the Default Stack Consumes So Much Memory

Prometheus wasn’t designed for 2GB RAM VPS deployments. It was designed for Kubernetes clusters with 8+ GB nodes and horizontal scaling. When you pull the standard Grafana Labs Docker Compose example — which, per Grafana’s official documentation, uses no memory limits by default — you’re running a config built for a completely different environment.

Out of the box, Prometheus allocates memory aggressively. Its TSDB uses a WAL (write-ahead log) that buffers 2-hour chunks in memory before compacting them. With even a modest scrape configuration — say, 500 time series scraped every 15 seconds — you’re looking at 300–500MB for Prometheus alone before Grafana touches a single byte.

Grafana adds its own weight. The application server, plugin loader, and background alerting evaluator together sit at 200–400MB depending on dashboard complexity. Node Exporter is lightweight — typically under 30MB — but it’s rarely the problem.

The math is brutal on a 2GB RAM VPS. Linux itself needs ~200MB. Docker’s overhead adds another 50–80MB. Before any of your monitoring stack is running, you’ve already spent ~280MB. The default Prometheus + Grafana Docker Compose stack can push you past 1.8GB combined RSS, leaving under 200MB headroom. A single memory spike — a Grafana dashboard query hitting a large time range, or Prometheus compacting a TSDB block — triggers the exact OOM crash you’re scrambling to fix at 4 AM.


The Six Configuration Changes That Actually Work

Memory Limits and Swap: The Safety Net

The first step isn’t tuning — it’s containment. Docker’s mem_limit and memswap_limit in your Compose file prevent any single container from consuming the entire host.

services:
  prometheus:
    mem_limit: 512m
    memswap_limit: 768m
  grafana:
    mem_limit: 256m
    memswap_limit: 384m

Per OneUptime’s Docker resource limits guide, setting memswap_limit to 1.5x your mem_limit gives containers a swap buffer that prevents immediate OOM kills during spikes while still protecting the host. Without this, Docker applies no ceiling whatsoever.

Prometheus TSDB Retention: Stop Hoarding Data

Prometheus’s default retention is 15 days with volume-unlimited storage. On a 2GB RAM VPS, this is the single biggest driver of memory growth over time. As the TSDB grows, Prometheus loads more index data into memory.

Two flags change everything:

command:
  - '--storage.tsdb.retention.time=7d'
  - '--storage.tsdb.retention.size=800MB'

The retention.size flag — available since Prometheus 2.7 — caps disk usage and prevents the TSDB from growing large enough that its in-memory index becomes a problem. Seven days of retention covers 95% of operational debugging needs. Fifteen days rarely gets used and costs real RAM.

Scrape Interval and Chunk Encoding

The default 15-second scrape interval generates 4 samples per minute per metric. On a system with 1,000 active time series, that’s 240,000 samples per hour sitting in the WAL before compaction. Stretching this to 30 seconds cuts WAL memory usage roughly in half — with minimal practical impact on alert resolution time.

global:
  scrape_interval: 30s
  evaluation_interval: 30s

Grafana: Kill the Background Bloat

Three Grafana environment variables cut memory consumption significantly:

environment:
  - GF_ANALYTICS_REPORTING_ENABLED=false
  - GF_PLUGINS_ENABLE_ALPHA=false
  - GF_AUTO_ASSIGN_ORG_ROLE=Viewer

Disabling analytics reporting stops background HTTP calls. Disabling alpha plugins prevents Grafana from loading experimental modules at startup. These two changes together drop Grafana’s idle RSS by roughly 60–80MB, per community benchmarks from the Docker Recipes Prometheus + Grafana guide.


Comparing Configuration Approaches for 2GB RAM VPS Deployments

ApproachPrometheus RSSGrafana RSSOOM RiskSuitable for 2GB VPS
Default (no limits, 15d retention, 15s scrape)450–600MB280–420MBHighNo
Limits only (mem_limit, no tuning)450–600MB280–420MBMedium (hard kill)Marginal
Full tuning (limits + 7d retention + 30s scrape)180–280MB160–220MBLowYes
Minimal (tuning + single exporter only)120–160MB140–180MBVery LowYes

The “limits only” approach is a common half-measure. Engineers add mem_limit and feel safe, but the container still runs at 550MB until Docker hard-kills it. Real stability requires both the ceiling and a reduction in what’s consuming memory underneath it.


What This Looks Like in Production: Problem/Solution Framing

Scenario 1 — Fresh deploy, immediate crash within 24 hours. The TSDB WAL fills memory during initial scrape ingestion. Fix: set retention.size=800MB and scrape_interval: 30s before the first run. Don’t let the database grow uncontrolled before adding limits.

Scenario 2 — Stack runs fine for two weeks, then OOM crash. Prometheus’s TSDB has grown large enough that its block index exceeds available RAM during a compaction cycle. Fix: add retention.time=7d to cap index growth. Consider dropping rarely-used metrics with metric_relabel_configs to reduce series cardinality.

Scenario 3 — Grafana crashes but Prometheus survives. Grafana hit its dashboard query memory ceiling. Fix: add mem_limit: 256m to the Grafana service and reduce dashboard time ranges. A 30-day time range query on a high-cardinality metric is a memory spike waiting to happen.

Watch for this signal: If docker stats shows Grafana consistently running above 200MB RSS on an idle system, plugins or background processes are consuming memory unnecessarily. Run docker exec grafana grafana-cli plugins ls to audit what’s loaded.


What’s Next

Getting a Prometheus + Grafana Docker Compose stack stable on a 2GB RAM VPS isn’t about picking one setting — it’s about stacking four or five targeted changes that each reduce memory pressure from a different angle.

The core findings hold up across deployments:

  • Default configurations leave no safe headroom on 2GB RAM VPS setups
  • TSDB retention size and scrape interval are higher-impact than Docker memory limits alone
  • Grafana’s background processes add 60–120MB of avoidable overhead that’s simple to disable
  • A real OOM crash fix requires both containment (limits) and reduction (tuning) — one without the other won’t hold

This approach can fail when your scrape targets grow significantly. If you add 10 new exporters six months in, the 30s interval and 7d retention may not be enough — revisit cardinality with promtool tsdb analyze before the next crash finds you first.

Looking ahead through 2026, Prometheus 3.x’s ongoing work on native histograms and reduced WAL memory usage should help constrain memory on resource-limited systems. Grafana 11’s plugin sandbox improvements also promise better memory isolation per plugin. But those gains won’t save an untuned stack on a 2GB machine — the configuration fundamentals still apply regardless of version.

Check your current docker stats output. If Prometheus is sitting above 350MB at idle, there’s room to fix that today.

References

  1. Prometheus + Grafana Docker Compose - Ready to Deploy | Docker Recipes
  2. Docker CPU & Memory Limits: Prevent Container Resource Exhaustion
  3. Monitoring a Linux host with Prometheus, Node Exporter, and Docker Compose | Grafana Cloud documenta

Photo by Surface on Unsplash