Why Self-Hosted Metrics Beat Cloud Dashboards
Monitoring your own infrastructure without shipping data to a third-party service is not just a privacy preference – it is a practical engineering decision. Prometheus and Grafana together give you a full observability stack that runs entirely on your hardware, costs nothing beyond server resources, and gives you query flexibility that most hosted monitoring tools charge a premium for. The combination has become the default choice for homelab operators and small engineering teams who want production-grade visibility without a SaaS subscription.
Prometheus is a time-series database that scrapes metrics from configured targets at regular intervals and stores them locally. Grafana is the visualization layer that connects to Prometheus as a data source and lets you build dashboards with flexible query editors and alert rules. Neither tool requires the other to function, but together they cover the full loop from data collection to human-readable display. This guide walks through installing both on a single Linux host using Docker Compose, configuring a basic scrape job, and building your first dashboard.

Installing Prometheus and Grafana with Docker Compose
Docker Compose is the fastest way to get both services running with consistent configuration. Create a project directory – something like /opt/monitoring – and inside it create your docker-compose.yml file. The compose file needs two services: one for Prometheus on port 9090 and one for Grafana on port 3000. You will also want named volumes for both so that your metrics data and Grafana dashboards persist across container restarts.
A minimal compose file looks like this. Under the Prometheus service, map a local prometheus.yml config file into the container at /etc/prometheus/prometheus.yml, and mount a volume to /prometheus for storage. Under Grafana, set the environment variable GF_SECURITY_ADMIN_PASSWORD to a strong password, and mount a volume to /var/lib/grafana. Set both services to restart unless-stopped so they survive reboots. Run docker compose up -d and both containers will pull their images and start.
Configuring the Prometheus Scrape File
Prometheus learns what to monitor through its prometheus.yml configuration file. The top-level sections you need are global, which sets the default scrape interval, and scrape_configs, which lists every target Prometheus will pull metrics from. A scrape interval of 15 seconds is a sensible starting point – short enough to catch spikes, not so aggressive that you fill disk faster than necessary.
The first scrape job you should add is Prometheus monitoring itself. Under scrape_configs, add a job named prometheus with a static target of localhost:9090. This is useful for confirming the whole pipeline works before you connect anything else. Once that is running, add Node Exporter as your second job. Node Exporter is a Prometheus-native agent that exposes host-level metrics like CPU usage, memory, disk I/O, and network throughput. Run it as a third container on port 9100 and add a corresponding scrape job pointing to its address.
With Node Exporter in place, your Prometheus instance will be collecting dozens of metrics about the host every 15 seconds. You can verify this by opening http://your-server-ip:9090 in a browser and running a basic PromQL query like node_cpu_seconds_total in the expression browser. If data points appear in the graph view, the scrape pipeline is working correctly.
Prometheus uses a pull model, which means it reaches out to targets rather than waiting for targets to push data in. This architecture makes it easy to add new monitored services by simply appending entries to scrape_configs and reloading the configuration – no agent reconfiguration needed. For services that cannot expose a metrics endpoint directly, Prometheus offers a push gateway component, though for most self-hosted setups the pull model covers everything you need.

Connecting Grafana to Prometheus
Open Grafana at http://your-server-ip:3000 and log in with the admin credentials you set in the compose file. Navigate to Connections – Data Sources and add a new Prometheus data source. In the URL field, enter http://prometheus:9090 – using the Docker service name rather than localhost, because Grafana and Prometheus are running in the same Docker network and can reach each other by container name. Click Save and Test and Grafana will confirm the connection is live.
From this point, every dashboard panel you create can query Prometheus using PromQL. The connection is persistent – Grafana stores the data source configuration in its own database, so it survives container restarts without reconfiguration. If you are running multiple environments, you can add additional Prometheus instances as separate data sources and switch between them per dashboard.
Building Your First Dashboard
Rather than building from scratch, the fastest way to get a useful dashboard running is to import one from the Grafana dashboard library. The Node Exporter Full dashboard, available at grafana.com/grafana/dashboards with ID 1860, covers virtually every system metric Node Exporter exposes – CPU by core, memory breakdown, disk usage per partition, network in and out, and system load averages. In Grafana, go to Dashboards – Import, enter the dashboard ID, select your Prometheus data source, and click Import. Within seconds you have a fully populated dashboard without writing a single PromQL query manually.
Once you are comfortable reading the pre-built panels, start customizing. Click into any panel’s edit view to see the underlying PromQL query. A query like 100 – (avg by(instance)(rate(node_cpu_seconds_total{mode=”idle”}[5m])) * 100) calculates CPU utilization as a percentage – reading it teaches you how PromQL’s rate function, label filtering, and aggregation operators work together. Modifying these queries is the fastest way to develop fluency with the language.
Grafana’s alerting system integrates directly with the same queries that power your panels. Under a panel’s alert tab, you can set threshold conditions – for example, triggering an alert when CPU usage exceeds 85 percent for more than two minutes. Alerts can route to email, Slack webhooks, or a self-hosted push notification server depending on what your setup includes. If you already run Ntfy as a self-hosted push notification server, you can wire Grafana alerts directly to it using a webhook contact point, keeping the entire notification chain off third-party infrastructure. The threshold configuration uses the same PromQL expression as the panel itself, so there is no translation layer between what you see on screen and what triggers your alerts.






