Why Self-Hosted Metrics Beat Cloud Dashboards
Sending your infrastructure metrics to a third-party cloud service means handing over granular data about your system behavior, load patterns, and failure points to someone else’s servers. For hobbyists running home labs, developers managing VPS fleets, or small teams self-hosting production workloads, that tradeoff rarely makes sense. Prometheus and Grafana together give you a full observability stack you control entirely – from data retention to dashboard access – running on hardware you already own.
Prometheus handles the collection and storage side: it scrapes metrics from your services at regular intervals and keeps them in a time-series database. Grafana connects to that database and turns raw numbers into readable dashboards. Neither tool requires a subscription, neither phones home with your data, and together they handle everything from a single Raspberry Pi to a multi-node Kubernetes cluster. This guide walks through standing both up from scratch on a Linux server using Docker Compose.

Prerequisites and Directory Layout
You need a Linux host with Docker and Docker Compose installed. The setup works on Ubuntu, Debian, Fedora, or any modern distribution running Docker Engine 20.10 or later. You should also have a non-root user with sudo access and ports 9090 and 3000 available – those are the default ports Prometheus and Grafana listen on respectively. If you are already running something on either port, you can remap them in the Compose file without any other changes.
Start by creating a working directory to keep everything organized. Run mkdir -p ~/monitoring/{prometheus,grafana/provisioning/{datasources,dashboards}} to build the full folder tree at once. This structure keeps your Prometheus configuration separate from your Grafana provisioning files, which matters once you start automating dashboard deployments. All file paths in this guide assume you are working from ~/monitoring as your project root.
Configuring Prometheus
Prometheus needs a configuration file that tells it what to scrape and how often. Create ~/monitoring/prometheus/prometheus.yml and open it in your editor. The minimal configuration below starts Prometheus scraping itself, which is useful for verifying the setup before you add real targets.
Paste this into the file:
- global: sets the default scrape interval and evaluation interval, both of which you can set to 15s for a responsive home lab setup
- scrape_configs: holds a list of jobs, each targeting a different service or exporter
- The first job should be named prometheus with a static target of localhost:9090
The full block looks like this:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
– job_name: “prometheus”
static_configs:
– targets: [“localhost:9090”]
Save the file. You will return here to add Node Exporter and any application-specific exporters once the base stack is running. Keeping the initial config minimal means you can confirm Prometheus starts cleanly before layering in more complexity. A misconfigured scrape target will not crash Prometheus, but bad global settings will prevent it from starting at all – so getting this file right first matters.

Writing the Docker Compose File
Create ~/monitoring/docker-compose.yml. This file defines both services, mounts your config files into the containers, and sets up a shared network so Grafana can talk to Prometheus by service name rather than IP address.
Here is the full Compose configuration:
- version: “3.8” at the top
- services: block containing both prometheus and grafana
- Prometheus image: prom/prometheus:latest, port mapping 9090:9090, volume mounting ./prometheus/prometheus.yml to /etc/prometheus/prometheus.yml, and a named volume prometheus_data at /prometheus
- Grafana image: grafana/grafana:latest, port mapping 3000:3000, environment variables GF_SECURITY_ADMIN_USER and GF_SECURITY_ADMIN_PASSWORD set to values you choose, and a named volume grafana_data at /var/lib/grafana
- Both services on a shared network named monitoring
- Named volumes block at the bottom declaring prometheus_data and grafana_data
Using named volumes instead of bind mounts for data directories means Docker manages the storage location and your data survives container rebuilds. The admin credentials you set in the environment block become Grafana’s login – write them down, or pass them in via a .env file if you prefer not to commit credentials to version control.
Starting the Stack and Adding Node Exporter
From inside ~/monitoring, run docker compose up -d. Docker pulls both images on the first run, which takes a minute or two depending on your connection. Once it completes, check that both containers are running with docker compose ps. You should see both prometheus and grafana listed with a status of Up.
Open a browser and go to http://your-server-ip:9090. The Prometheus web interface loads a basic query UI. Navigate to Status – Targets and confirm the prometheus job shows as UP. That single green badge confirms the scrape pipeline is working end to end. Now add Node Exporter to collect actual host metrics. Add this block to your docker-compose.yml under services:
- Service name: node-exporter
- Image: prom/node-exporter:latest
- Port mapping: 9100:9100
- Volume mount: /proc:/host/proc:ro, /sys:/host/sys:ro, /:/rootfs:ro
- Command flags: –path.procfs=/host/proc –path.sysfs=/host/sys –collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)
- Same monitoring network
Then add a new job to prometheus.yml pointing at node-exporter:9100. Run docker compose up -d again to apply the changes. Prometheus restarts with the new config and begins scraping CPU, memory, disk, and network metrics from your host within seconds.
Connecting Grafana and Loading a Dashboard

Open http://your-server-ip:3000 and log in with the credentials you set in the Compose file. Go to Connections – Data Sources and click Add data source. Select Prometheus, then set the URL to http://prometheus:9090 – using the service name works because both containers share the same Docker network. Click Save and Test and Grafana will confirm the connection is live.
For an instant dashboard covering all the Node Exporter metrics you are now collecting, go to Dashboards – Import and enter dashboard ID 1860. This is the widely-used Node Exporter Full dashboard maintained in the Grafana dashboard library. Select your Prometheus data source during import and click Import. The dashboard loads immediately and starts populating with live data – CPU usage, load averages, memory breakdown, disk I/O, and network throughput, all visualized without writing a single PromQL query manually.
From this point, adding monitoring for other self-hosted services follows the same pattern: find or run the appropriate exporter, expose its port, add a scrape job to prometheus.yml, reload the config with docker compose restart prometheus, and import a matching Grafana dashboard. If you are already running Ntfy as a self-hosted push notification server, you can wire Grafana alerting directly into it so threshold breaches send you a push notification without routing through any external service. The same data Grafana is already reading becomes the trigger – no extra infrastructure needed.
Frequently Asked Questions
Do I need Docker to run Prometheus and Grafana?
Docker is the easiest path, but both tools offer native binaries for Linux. Docker Compose simply makes managing both services together much faster.
How do I add more services to Prometheus monitoring?
Run the appropriate exporter for your service, expose its metrics port, then add a new scrape job pointing at that port in your prometheus.yml file.





