Free Self-Hosted Alternatives to Datadog: What Actually Works on Your Own Hardware
TL;DR: Datadog's per-host pricing is deliberately structured to feel cheap at two or three nodes and expensive by the time you actually need it. The base infrastructure plan charges per host, per month, and that cost multiplies with every add-on โ APM, log management, synthetics โ each ๐ Reading t

TL;DR: Datadog's per-host pricing is deliberately structured to feel cheap at two or three nodes and expensive by the time you actually need it. The base infrastructure plan charges per host, per month, and that cost multiplies with every add-on โ APM, log management, synthetics โ each ๐ Reading time: ~21 min Why Datadog's Pricing Forces the Self-Hosting Conversation The Five Contenders and Their Honest Resource Costs Setting Up Prometheus + Grafana in Docker Compose VictoriaMetrics as a Prometheus Drop-In: Where It Wins Netdata for Operators Who Want Zero Configuration Matching Tool to Situation: The Decision Framework Common Failure Modes and How to Catch Them Early Datadog's per-host pricing is deliberately structured to feel cheap at two or three nodes and expensive by the time you actually need it. The base infrastructure plan charges per host, per month, and that cost multiplies with every add-on โ APM, log management, synthetics โ each billed separately. Once you're past a handful of nodes, the invoice scales faster than your infrastructure does. The free tier lets you evaluate the product convincingly, then cuts retention to one day and limits custom metrics aggressively enough that real alerting becomes impossible without upgrading. The specific operator problem isn't abstract cost anxiety โ it's that you need continuous visibility into memory pressure, disk saturation rates, container restart loops, and traffic anomalies across your own hardware, and you don't want that telemetry leaving your network. Sending host-level metrics to a third-party SaaS means your capacity patterns, failure modes, and workload fingerprints are visible to another company's infrastructure. For homelab operators running LLM workloads or anything with sensitive data in memory, that's a hard no before you even look at the bill. What self-hosted monitoring actually demands breaks down into three functional pieces that you can run as a monolith or compose from separate tools: A metrics store with sustainable retention โ something that handles 30-day or 90-day TSDB retention without OOMing on a modest VM. Prometheus with its default in-memory index is fine at small cardinality but will eat RAM if you're scraping hundreds of containers with high-label-cardinality metrics. VictoriaMetrics handles the same workload with a much smaller heap. A dashboard layer โ Grafana is the obvious answer here, and it talks to nearly every backend. The question is whether you want dashboards bundled with the metrics store (Netdata does this) or composable (Prometheus + Grafana). An alerting engine โ Prometheus AlertManager is the standard, but it's configuration-heavy. Tools like Grafana Alerting or Netdata's built-in alerting give you something usable without writing YAML state machines from scratch. The composable route gives you more control and survives tool swaps. The monolith route (a single tool that does all three) gets you to alerting faster but makes migration painful later. Neither is universally correct โ it depends on whether you're instrumenting three personal servers or managing a dozen-node homelab with mixed workloads. For readers also evaluating AI-assisted dev tooling in their stack, see our guide on AI Coding Tools in 2026: Cloud Copilots vs Local Models. The most common trap when replacing Datadog is reaching for Prometheus + Grafana immediately because every tutorial points there โ without accounting for what that stack actually costs at rest. Prometheus memory usage is governed almost entirely by cardinality: the number of unique label combinations across all your metrics. A modest home lab scraping 10 exporters with aggressive labeling (pod name, container name, instance, job, environment, region) can push RAM well above 1GB before you've connected a single dashboard. On my 32GB workstation this is invisible, but on a 4GB VPS it's the first thing that bites. Budget 500MB as an absolute floor for a minimal Prometheus instance and expect 1.5โ2GB once you add node_exporter, cAdvisor, and a few application exporters with non-trivial label sets. VictoriaMetrics solves exactly that problem. The single-node binary (victoria-metrics) is a drop-in Prometheus remote_write target and also speaks PromQL natively, so your existing Grafana dashboards require zero changes. The compression ratio compared to Prometheus's TSDB is genuinely dramatic โ the same time series that chews through 10GB of disk in Prometheus will often land under 3GB in VictoriaMetrics. More importantly, its memory ceiling is roughly proportional to active query complexity, not raw cardinality, which means you can throw high-cardinality Kubernetes label sets at it without triggering OOM kills. It ships with its own alerting engine (vmalert) so you can drop Alertmanager from the stack entirely if you want a simpler footprint. Netdata occupies a completely different design space. Rather than a scrape-then-store model, it runs as a streaming agent with 1-second resolution out of the box, auto-discovers Docker containers, systemd services, and dozens of application plugins without any configuration. The agent itself requires no cloud account โ the Netdata Cloud UI is optional and the local dashboard runs fully offline at http://localhost:19999. The trade-off is that local retention defaults to roughly 1 day of per-second data (configurable via the dbengine settings in netdata.conf), so Netdata excels at real-time visibility and short-term debugging but isn't the right fit if you need 90-day trend analysis. RAM footprint on a typical node runs 80โ150MB, which makes it genuinely viable on a Raspberry Pi 4. Checkmk Raw Edition comes from a different heritage entirely โ enterprise infrastructure monitoring rather than cloud-native metrics pipelines. The free tier has no host limit, which is unusual and worth taking seriously. The install footprint is heavier: it runs its own Apache instance, a Python-based check engine, and an OMD (Open Monitoring Distribution) site structure that isolates itself from your system packages. Expect 300โ500MB RAM at idle for a single site. What you get in return is strong host state monitoring โ service checks, hardware health via SNMP, Windows agent support โ that Prometheus + Grafana handles awkwardly through a patchwork of exporters. If your environment mixes Linux servers, network gear, and Windows machines and you care more about "is this thing up and healthy" than "show me p99 latency histograms," Checkmk fits better than the metrics-first stack. Here's how they compare on the dimensions that actually matter for small self-hosters: Tool Storage Backend Min RAM (realistic) Retention Default Docker Support Alerting Built-in Biggest Dealbreaker for Small Self-Hosters Prometheus + Grafana Custom TSDB (local) 500MBโ2GB 15 days Via cAdvisor exporter Alertmanager (separate process) Cardinality spikes OOM without warning; five separate processes to maintain VictoriaMetrics Custom TSDB (local) 200โ400MB 1 month (configurable) Via cAdvisor or Prometheus exporters vmalert (built-in) No native dashboards โ still need Grafana for visualization Netdata dbengine (local, tiered) 80โ150MB ~1 day per-second, longer at lower resolution Auto-discovers without config Yes (health.d rules) Long-term retention requires significant disk + tuning; PromQL not supported natively Checkmk Raw RRDtool 300โ500MB 2 years (RRD, fixed-size, downsampled) Agent or SNMP; container discovery not as automatic Yes (native, rule-based) RRDtool pre-aggregates data โ you cannot query raw samples after the fact The Checkmk RRDtool point deserves emphasis: RRDtool is a fixed-size circular buffer that downsamples older data automatically. After a week, your 1-minute resolution data gets consolidated into 5-minute averages and the originals are gone. For host-state monitoring this is fine. For post-incident forensics where you need to replay exactly what a metric did at 3:47 AM two weeks ago, it's a hard wall. Prometheus, VictoriaMetrics, and Netdata's dbengine all retain raw samples, which is the correct default for anything beyond basic uptime checks. The stack that trips up most first-timers isn't Prometheus itself โ it's the combination of volume mount paths that silently reset on container restart, Grafana's security defaults that give you a blank login page with zero log output, and a prometheus.yml that scrapes nothing because the service name doesn't match what you think it does. Start with the compose file and get all three services talking before touching dashboards. # docker-compose.yml version: "3.8" services: prometheus: image: prom/prometheus:v2.52.0 container_name: prometheus restart: unless-stopped ports: - "9090:9090" volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.path=/prometheus" - "--storage.tsdb.retention.time=30d" - "--storage.tsdb.retention.size=10GB" - "--web.enable-lifecycle" # lets you POST /-/reload without restarting grafana: image: grafana/grafana:10.4.2 container_name: grafana restart: unless-stopped ports: - "3000:3000" volumes: - grafana_data:/var/lib/grafana environment: - GF_AUTH_ANONYMOUS_ENABLED=true - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer - GF_SECURITY_ADMIN_PASSWORD=changeme # still sets the admin account depends_on: - prometheus node_exporter: image: prom/node-exporter:v1.8.0 container_name: node_exporter restart: unless-stopped ports: - "9100:9100" volumes: # read-only host mounts so node_exporter sees real filesystem/proc data - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - "--path.procfs=/host/proc" - "--path.sysfs=/host/sys" - "--path.rootfs=/rootfs" - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)" volumes: prometheus_data: grafana_data: # prometheus/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: "prometheus" static_configs: - targets: ["localhost:9090"] - job_name: "node_exporter" # use the Docker service name โ not localhost, not the host IP static_configs: - targets: ["node_exporter:9100"] The GF_AUTH_ANONYMOUS_ENABLED=true env var is the non-obvious one. Grafana 10.x ships with anonymous access off by default, and when you hit port 3000 on first load without it, you get a login form โ which is expected โ but if you're reverse-proxying through nginx or Tailscale, the redirect chain fails silently and you see a blank page or a 302 loop. Setting GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer alongside it means unauthenticated users land on dashboards read-only, which is fine for a home lab or internal tool. If you want to lock it down later, flip the env var and bounce the container โ no data loss since everything lives in the named volume. For Alertmanager, the routing tree is where most people create their own alert storms. The defaults are aggressive. Here's a skeleton that actually behaves: # alertmanager/alertmanager.yml global: resolve_timeout: 5m route: group_by: ["alertname", "job"] group_wait: 30s # wait this long before sending the first alert in a group group_interval: 5m # how long to wait before sending new alerts in an existing group repeat_interval: 4h # don't re-fire a still-active alert more often than this receiver: "slack-ops" receivers: - name: "slack-ops" slack_configs: - api_url: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" channel: "#alerts" send_resolved: true title: '{{ template "slack.default.title" . }}' text: >- {{ range .Alerts }} *Alert:* {{ .Annotations.summary }} *Details:* {{ range .Labels.SortedPairs }} {{ .Name }}={{ .Value }} {{ end }} {{ end }} inhibit_rules: - source_match: severity: "critical" target_match: severity: "warning" equal: ["alertname", "job"] # suppress warning if critical is already firing The group_wait: 30s / group_interval: 5m combination is what saves you from getting 40 Slack messages during a single node bounce. group_wait batches the initial burst; group_interval controls how long Alertmanager waits before sending updates to an already-notified group. Set repeat_interval below 1h and you will regret it during any multi-hour incident. On storage: setting only --storage.tsdb.retention.time=30d means Prometheus will happily consume unbounded disk if your scrape cardinality spikes โ a misbehaving exporter dumping thousands of label combinations will fill a volume in hours with no obvious warning until the container OOMs or the disk hits 100%. Set --storage.tsdb.retention.size=10GB alongside the time flag and Prometheus enforces whichever limit triggers first. On a typical single-host setup scraping node_exporter plus a few app exporters at 15s intervals, 10GB covers well over 30 days โ but if you add Kubernetes pod metrics or high-cardinality app labels, that number compresses fast. Run prometheus_tsdb_head_series as a metric to watch cardinality in real time; anything above 500k active series on a single Prometheus instance starts hurting query latency noticeably. The single-binary story is genuinely what gets you to try it. One Docker command and you have a running TSDB that accepts Prometheus remote_write without touching your scrape configs: docker run -d \ --name victoria-metrics \ -v /path/victoria-metrics-data:/victoria-metrics-data \ -p 8428:8428 \ victoriametrics/victoria-metrics:v1.101.0 \ --retentionPeriod=6 # months, not days Then in your existing prometheus.yml: remote_write: - url: http://your-vm-host:8428/api/v1/write # keep your existing Prometheus scraping โ VM just receives the data High-cardinality label sets that were making Prometheus grind โ think per-pod labels with UUID suffixes โ compress dramatically under VictoriaMetrics's storage engine. You don't have to tune anything; the headaches just shrink. The disk and memory difference is real and consistent enough to plan around. VictoriaMetrics's custom compression achieves 3โ7x less disk usage than Prometheus's TSDB on comparable datasets โ this is documented behavior from the project, reproducible by pointing both at the same remote_write stream and watching du -sh over a week. More practically: Prometheus holds its entire index in RAM. VictoriaMetrics doesn't need to, so its memory ceiling is lower under the same cardinality. On a constrained host โ a small VPS or a shared homelab node โ that difference determines whether the process survives a traffic spike. VMAlert handles alerting rules and the compatibility story is mostly painless. Your existing Prometheus alerting rule YAML runs without modification in the common case. You point VMAlert at your VictoriaMetrics datasource, wire it to a notifier (Alertmanager if you have it, or a direct webhook if you don't), and you're done. For setups where you don't want to run a full Alertmanager stack, VMAlert can call a webhook directly โ enough to cover Slack, PagerDuty, or whatever HTTP endpoint you're using for notifications. # vmalert minimal config pointing at VM + direct webhook notifier ./vmalert \ --datasource.url=http://localhost:8428 \ --remoteWrite.url=http://localhost:8428 \ --notifier.url=http://your-webhook-endpoint/alert \ --rule=/etc/vmalert/rules/*.yml Where VictoriaMetrics bites you: its PromQL dialect diverges just enough to break some Grafana dashboards built around Prometheus-specific functions. histogram_quantile with native histograms, certain label_replace edge cases, and subquery syntax can behave differently or fail silently โ returning empty results rather than an error, which is the worst kind of failure for a monitoring dashboard. Before migrating any production dashboards, run both Prometheus and VictoriaMetrics in parallel on the same data stream, load every dashboard against both datasources, and diff the panel outputs. The incompatibilities are rarely showstoppers but they're also rarely obvious until a dashboard goes blank during an incident. Most monitoring tools front-load the configuration burden โ you spend an hour writing scrapers before seeing a single graph. Netdata inverts that. Run this on a bare Linux host and you'll have a full dashboard in under a minute: wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/netdata-kickstart.sh That single command installs the agent, detects your OS, and starts streaming CPU per-core, memory pressure, disk I/O, network throughput, and swap at 1-second granularity. No YAML, no scrape targets, no exporters. The dashboard is live at http://your-host:19999 before you've had time to open the docs. For operators who've wrestled with Prometheus + Grafana setup just to watch a box โ this contrast is jarring in a good way. Docker support is equally low-friction. The agent reads /var/run/docker.sock and automatically surfaces per-container cgroup metrics โ CPU, memory limits vs. actual usage, network I/O โ without requiring any label annotations or compose file edits. On my workstation running a dozen containers through Docker, every one of them showed up named and graphed within seconds of the agent starting. The one gotcha: the agent process needs socket access, so if you're running Netdata itself in a container you need to mount the socket explicitly. Running it on the host directly sidesteps the whole problem. The storage behavior trips up most first-time users. Out of the box, Netdata stores metrics in a RAM ring-buffer โ roughly one hour of history, gone on restart. For lab use that's fine; for anything production-adjacent it's a problem. The fix is switching to dbengine mode, which persists to disk with a configurable cap. Edit /etc/netdata/netdata.conf: [db] mode = dbengine # storage tiers control how long data is kept at each resolution storage tiers = 3 # tier 0: 1-second granularity, cap in MiB dbengine multihost disk space MB = 1024 With mode = dbengine and a 1 GB disk cap you get several days of 1-second data for a moderately busy host. The tiering system downsamples older data automatically โ tier 1 goes to per-minute aggregates, tier 2 to hourly โ so you're not burning disk storing raw second-level data indefinitely. This is in the docs but buried several pages deep, which is why a lot of people assume Netdata just doesn't do persistence. The alert system is where the tool shows its seams. Rules live in /etc/netdata/health.d/*.conf and the format is Netdata-specific โ not compatible with Prometheus alerting rules and not transferable if you ever switch stacks. Simple threshold alerts are genuinely readable: alarm: disk_space_usage on: disk.space lookup: average -10m percentage of used units: % every: 1m warn: $this > 80 crit: $this > 90 info: disk space usage on $label:mount_point For that kind of alert โ one metric, one threshold, notify when breached โ the format is cleaner than writing a PromQL expression. But if you need composite conditions (alert when CPU is high AND disk I/O is saturated AND available memory is below X), the rule language gets awkward fast. You can reference other alarms and use $this substitution, but there's no join-like construct across different collectors the way PromQL handles it natively. If your alerting needs are threshold-based on individual metrics, Netdata's built-in rules are genuinely usable. If you're building multi-signal correlation alerts, you'll hit the ceiling quickly and probably end up routing to an external system anyway. Most people spend hours comparing dashboards before asking the question that actually narrows the field: how many nodes, and how long does the data need to live? Those two numbers eliminate more options than any feature matrix. Netdata's dbengine mode stores metrics compressed on disk rather than purely in RAM, so you're not trading retention for node count at small scale. The install is a single command, the auto-detection of processes, containers, and network interfaces works without touching a config file, and the built-in dashboards are genuinely useful out of the box โ no Grafana provisioning, no data source wiring. The trade-off: Netdata's alerting is passable but not composable. If you need routing rules that send disk alerts to one channel and app latency to another, you'll hit the ceiling fast. For a handful of hosts where you want eyes on the system in under an hour, nothing else comes close on setup time. # Netdata one-liner install โ works on Debian, Ubuntu, RHEL, Fedora wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh sh /tmp/netdata-kickstart.sh --stable-channel --disable-telemetry # Confirm dbengine is active (default since v1.23) grep -i dbengine /etc/netdata/netdata.conf # Expected: [db] mode = dbengine VictoriaMetrics single-node binary accepts Prometheus remote_write from any scrape agent, compresses metrics better than stock Prometheus on disk, and runs with a fraction of the memory overhead at this node count. You drop the existing Grafana dashboards on top, point the data source at the VictoriaMetrics HTTP port, and you're done. There's no cluster mode to configure, no separate compaction job to monitor, and retention is a single startup flag: -retentionPeriod=3 for three months. The ops burden is genuinely low โ it restarts cleanly, the WAL handles ungraceful shutdowns, and you don't need a sidecar to manage block lifecycle. Where it doesn't help: if your org already has Thanos running or you need multi-tenant query isolation, you're better off staying in the Prometheus ecosystem and not bifurcating your storage layer. # Minimal VictoriaMetrics single-node with 90-day retention docker run -d \ --name victoriametrics \ -p 8428:8428 \ -v /data/vm:/storage \ victoriametrics/victoria-metrics:v1.101.0 \ -storageDataPath=/storage \ -retentionPeriod=3 # months # Prometheus scrape agent remote_write config remote_write: - url: http://victoriametrics:8428/api/v1/write queue_config: max_samples_per_send: 10000 The setup overhead is real โ you're writing scrape configs, wiring Alertmanager receivers, tuning recording rules โ but the ecosystem payoff is also real. Every non-trivial open source project ships a Prometheus exporter. Recording rules let you pre-compute expensive aggregations at scrape time so dashboards stay fast. Alertmanager's routing tree handles deduplication, grouping, and inhibition in ways that YAML-configured alternatives simply don't match. And if you ever need to scale retention, the Thanos sidecar drops onto an existing Prometheus deployment without requiring you to migrate your metrics format or re-instrument your apps. Accept that the first weekend of config is genuinely fiddly. The compounding value of the exporter ecosystem is worth it once you pass a handful of distinct services. Prometheus exporters for Windows exist but they're a second-class experience โ the WMI exporter requires PowerShell provisioning, the cardinality explodes with IIS or SQL Server metrics, and you end up writing PromQL that nobody else on your team can read. Checkmk's agent installs as an MSI, auto-discovers services, and speaks the same check language across Linux, Windows, and SNMP devices. The Raw Edition (fully open source, no node limit beyond what your hardware handles) gives you service-state monitoring โ is this Windows service running, is this SNMP interface up โ which is categorically different from metric-threshold alerting. The install is heavier: it runs its own web interface, its own notification engine, and an OMD-based site structure that takes getting used to. That's the trade you make for a tool that actually handles the breadth of a mixed infrastructure without needing a different agent per platform. The failure that kills self-hosted monitoring setups fastest isn't a missing feature โ it's Prometheus cardinality explosion. Add one exporter that stuffs a request ID, trace ID, or UUID into a label value and you've created a new time series per request. Prometheus holds all active series in memory, so this compounds fast: a moderately busy API endpoint with request IDs in labels can generate millions of unique series within hours. The OOM kill comes with no warning and no useful log message, just a dead process. The fix is to audit before you ship. Hit :9090/tsdb/status in a browser and look at the "Top 10 series by label name" table โ it shows which labels are contributing the most cardinality. If you see anything unbounded at the top, fix the exporter config or relabeling rules before it reaches production. Specifically, use metric_relabel_configs in your scrape config to drop or hash the offending label: metric_relabel_configs: - source_labels: [request_id] action: labeldrop # dropping unbounded label before TSDB ingestion # hashing instead of dropping: action: replace, target_label: request_id, replacement: "redacted" Grafana data source drift is subtle enough that it doesn't announce itself โ panels just go empty and the error message is usually something useless like "No data." This happens constantly after a VictoriaMetrics migration because VM and Prometheus have different data source UIDs in Grafana, and any dashboard that hardcoded a UID internally now points at nothing. The reliable fix is to stop letting Grafana manage data source configuration interactively. Provision it via YAML with an explicit, stable UID: # /etc/grafana/provisioning/datasources/victoriametrics.yaml apiVersion: 1 datasources: - name: VictoriaMetrics type: prometheus uid: my-vm-ds-uid-001 # hardcoded โ same across restores, migrations, upgrades url: http://victoriametrics:8428 access: proxy isDefault: true With that file in place, the UID survives container rebuilds and database restores. Any dashboard JSON that references my-vm-ds-uid-001 will resolve correctly regardless of what Grafana's internal state thinks. Without this, every environment rebuild risks silent dashboard rot. Netdata's default alert thresholds are tuned for general-purpose Linux servers, which means they'll fire constantly on machines doing unusual workloads. Running Ollama with a large model loaded keeps GPU VRAM pinned near saturation by design โ that's the point โ but the memory pressure also shows up in system memory metrics and triggers memory.available alerts in a loop. The right fix isn't silencing all alerts, it's retuning specifically for that machine. Override the threshold in /etc/netdata/health.d/ram.conf (create it if it doesn't exist โ overrides in /etc/netdata/health.d/ take precedence over /usr/lib/netdata/conf.d/): # /etc/netdata/health.d/ram.conf alarm: ram_available on: system.ram lookup: average -1m percentage of avail units: % every: 1m warn: $this < 5 # drop from default ~20% to 5% for inference workloads crit: $this < 2 info: percentage of available RAM โ tuned down for Ollama host Checkmk's default active check interval looks reasonable on paper until you have 50+ hosts and start watching monitoring server CPU. Active checks โ SSH-based checks, custom scripts, anything that spawns a process โ stack up fast. The monitoring server is running one check process per host per service per minute. For non-critical services (disk space on a dev box, certificate expiry checks with 90-day certs), bump the check interval to 5 or 10 minutes in the service configuration. In Checkmk's Setup UI, that's under the "Normal check interval for service checks" rule in the ruleset browser, scoped to a host tag so you don't accidentally slow down critical path checks. The practical difference between a 1-minute and 5-minute interval for "disk usage on the NAS" is zero operational value โ the difference in CPU headroom on a small monitoring host is measurable. Disclaimer: This article is for informational purposes only. The views and opinions expressed are those of the author(s) and do not necessarily reflect the official policy or position of Sonic Rocket or its affiliates. Always consult with a certified professional before making any financial or technical decisions based on this content. Originally published on techdigestor.com. Follow for more developer-focused tooling reviews and productivity guides.
Key Takeaways
- โขTL;DR: Datadog's per-host pricing is deliberately structured to feel cheap at two or three nodes and expensive by the time you actually need it
- โขThis story was reported by Dev.to, covering developments in the dev space.
- โขAI advancements continue to reshape industries โ read the full article on Dev.to for complete coverage.
๐ Continue reading the full article:
Read Full Article on Dev.to โShare this article


