Skip to Content

See Everything: How to Self-Host Grafana for Production Monitoring

Deploy a complete observability stack with Grafana, Prometheus, and Loki using Docker Compose on your own infrastructure.

Every production system generates signals: CPU spikes, error logs, request latencies, disk pressure. Without a central place to see them, you are flying blind. Grafana is the open-source visualization layer that turns raw metrics and logs into dashboards you can actually use. Pair it with Prometheus for metrics and Loki for logs, and you have a self-hosted observability stack that rivals paid services at zero recurring cost.

This guide covers a complete self-host Grafana deployment using Docker Compose. You will collect system metrics, aggregate application logs, and build your first dashboard. For alternative setups, see our complete developer guide or the expanded monitoring deep dive.

What You Need Before Starting

You do not need a Kubernetes cluster or a dedicated observability team. A single VPS handles small to medium workloads:

  • A Linux server with at least 2 vCPUs, 4 GB RAM, and 30 GB SSD
  • Docker Engine 24.x+ and Docker Compose v2 installed
  • A domain or subdomain for Grafana (optional but recommended for TLS)
  • Basic familiarity with YAML, Prometheus query language, and Linux

Plan for storage growth. Metrics and logs accumulate fast. A 30-day retention policy with 10 containers can consume 10 GB per month.

Project Structure and Docker Compose

Create a project directory and pull the official images. We will run Grafana, Prometheus, Loki, Promtail, and Node Exporter as a single Compose stack:

sudo mkdir -p /opt/monitoring
sudo chown $USER:$USER /opt/monitoring
cd /opt/monitoring

mkdir -p {prometheus,loki,grafana/provisioning/datasources}

Create the docker-compose.yml:

services:
  prometheus:
    image: prom/prometheus:v2.53.0
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus:/etc/prometheus
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.enable-lifecycle'
    networks:
      - monitoring

  loki:
    image: grafana/loki:3.0.0
    container_name: loki
    restart: unless-stopped
    volumes:
      - ./loki:/etc/loki
    ports:
      - '127.0.0.1:3100:3100'
    command: -config.file=/etc/loki/loki-config.yml
    networks:
      - monitoring

  promtail:
    image: grafana/promtail:3.0.0
    container_name: promtail
    restart: unless-stopped
    volumes:
      - /var/log:/var/log:ro
      - ./loki:/etc/loki
    command: -config.file=/etc/loki/promtail-config.yml
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:11.0.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - '127.0.0.1:3000:3000'
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:v1.8.0
    container_name: node-exporter
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
    networks:
      - monitoring

volumes:
  prometheus-data:
  grafana-data:

networks:
  monitoring:
    driver: bridge

We bind Grafana and Loki to localhost so they are only reachable through a reverse proxy. Prometheus and Node Exporter have no exposed ports at all — they communicate internally.

Configuring Prometheus and Loki

Create the Prometheus scrape configuration:

# prometheus/prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

Create a minimal Loki configuration:

# loki/loki-config.yml
auth_enabled: false

server:
  http_listen_port: 3100

ingester:
  lifecycler:
    address: 127.0.0.1
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
  chunk_idle_period: 5m
  chunk_retain_period: 30s

schema_config:
  configs:
    - from: 2020-05-15
      store: boltdb
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 168h

storage_config:
  boltdb:
    directory: /tmp/loki/index
  filesystem:
    directory: /tmp/loki/chunks

Create the Promtail configuration to ship /var/log to Loki:

# loki/promtail-config.yml
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: system-logs
    static_configs:
      - targets: [localhost]
        labels:
          job: varlogs
          __path__: /var/log/**/*.log

Provisioning Grafana Datasources

Grafana can auto-configure datasources on startup. Create a provisioning file so Prometheus and Loki are ready immediately:

# grafana/provisioning/datasources/datasources.yml
apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true

  - name: Loki
    type: loki
    access: proxy
    url: http://loki:3100

Starting the Stack and First Dashboard

Launch everything:

docker compose up -d
docker compose ps

Verify Prometheus is scraping targets:

curl -s http://127.0.0.1:9090/api/v1/targets | jq '.data.activeTargets[] | {job, health}'

Open Grafana at https://grafana.example.com (or http://127.0.0.1:3000 if testing locally). The default credentials are admin / admin. Change the password immediately.

Import the official Node Exporter Full dashboard (ID: 1860) from the Grafana dashboards repository. It gives you CPU, memory, disk, and network panels without writing a single query.

Log Exploration with Loki

Switch to the Explore view in Grafana, select the Loki datasource, and run a simple query:

{job="varlogs"} |= "error"

This filters all system logs for lines containing "error". Combine with Grafana's log volume histogram to spot error spikes visually. For structured application logs, add labels in Promtail's relabel_configs to filter by service name or severity.

Tips and Troubleshooting

No metrics appearing in Grafana

Check that Prometheus can reach its targets. The expression browser at /graph on port 9090 shows scrape status. If Node Exporter is down, verify the container is on the monitoring network.

Loki returns 500 errors

The filesystem storage backend in this guide is for single-node setups. For production, switch to S3, GCS, or a shared filesystem. Also ensure the /tmp/loki paths inside the container are writable.

Promtail is not shipping logs

Confirm the __path__ glob matches your actual log files. Promtail does not follow symlinks by default. If your logs live in /var/log/journal, use the journald scrape config instead of file targets.

Grafana password reset

If you forget the admin password, reset it from the container:

docker exec -it grafana grafana-cli admin reset-admin-password newpassword

Dashboard import fails

Some community dashboards require specific exporter versions or additional metrics. Check the dashboard documentation for required collectors. The Node Exporter Full dashboard works with the default flags shown above.

High memory usage

Prometheus memory scales with the number of active time series. Reduce cardinality by dropping unnecessary labels, or increase the scrape interval. Loki memory spikes during heavy log ingestion — add rate limits in Promtail if needed.

Next Steps

You now have a self-hosted observability stack: Prometheus collecting metrics, Loki aggregating logs, and Grafana visualizing both. This foundation scales from a single VPS to a multi-node cluster by swapping storage backends and adding remote-write endpoints.

For more detailed guidance, explore our related posts:

Need help designing SLO dashboards, setting up alerting, or scaling your observability stack to multiple clusters? Contact our team for enterprise Grafana consulting and managed monitoring infrastructure.

OpenClaw Docker VPS Deploy: A Developer's Step-by-Step Guide
Get OpenClaw running on your own VPS with Docker Compose, secure domain access, and messaging channels configured in under 30 minutes.