Self-Hosting Grafana: A Complete Guide to Monitoring Dashboards and Observability

Grafana is an open-source observability platform for building dashboards, alerts, and operational views from metrics, logs, traces, and databases. This guide walks through self-hosting Grafana on Ubuntu 24.04 LTS with Docker Compose, Nginx, HTTPS, persistent storage, provisioning, backups, and safe day-two operations.

 Grafana self-hosted observability dashboard

Caption: Grafana gives teams a private observability workspace for dashboards, alerts, and operational views across infrastructure and applications.

Introduction

Grafana is an open-source observability and data visualization platform. It is best known for dashboards, but a real Grafana deployment often becomes the place where operators review metrics, logs, traces, alert history, service health, database trends, and application signals. It connects to data sources such as Prometheus, Loki, Tempo, Elasticsearch, InfluxDB, PostgreSQL, MySQL, and many hosted monitoring backends.

Self-hosting Grafana is useful when monitoring data should stay close to your own infrastructure. A small VPS, homelab, agency platform, or internal engineering team can run Grafana behind its own domain, control who signs in, decide which plugins are installed, version-control dashboards, and back up the application database with the rest of the stack. You also avoid scattering operational dashboards across personal accounts or unmanaged SaaS workspaces.

This guide installs Grafana Open Source on Ubuntu 24.04 LTS with Docker Compose. The deployment stores Grafana data in a named Docker volume, binds the container to localhost, places Nginx in front for HTTPS, prepares provisioning directories for data sources and dashboards, and adds a practical backup routine. By the end, Grafana will be available at https://grafana.example.com with a secure admin password, a repeatable Compose file, and a checklist for creating your first monitoring dashboard.

Why Choose Grafana?

  • Flexible dashboards: Grafana can combine time series panels, tables, gauges, stat cards, logs, annotations, and variables into views that make sense for your services.
  • Broad data source support: Prometheus, Loki, Tempo, Elasticsearch, InfluxDB, PostgreSQL, MySQL, and many cloud services can be connected from one interface.
  • Configuration as code: Data sources and dashboard providers can be provisioned from files, which is ideal for Git-managed infrastructure.
  • Alerting workflows: Grafana includes alert rules, contact points, notification policies, silences, and alert state history.
  • Open-source deployment option: The grafana/grafana image runs the open-source edition and works well for private observability stacks.
  • Docker-friendly operations: The app can run as a single container with persistent storage and a reverse proxy in front.
  • Strong fit for small teams: A single Grafana server can provide shared visibility for application owners, sysadmins, support engineers, and homelab operators.

Grafana is not a metrics collector by itself. It visualizes and manages data that comes from other systems. For infrastructure metrics, pair it with Prometheus, node_exporter, cAdvisor, or another collector. For logs, pair it with Loki or another supported log data source. Start with one clean data source, a few useful dashboards, and reliable backups before expanding into a full observability platform.

Prerequisites

Hardware Recommendations:

  • 1 CPU core and 1 GB RAM for a personal Grafana instance with a few dashboards
  • 2 CPU cores and 2 GB RAM for a small team using multiple data sources and alerts
  • 20 GB free disk space to start, plus room for the Grafana database, plugins, dashboard snapshots, logs, and backups
  • SSD-backed storage for smoother dashboard loading and plugin operations
  • Off-server storage for configuration and volume backups

Software and Accounts:

  • Ubuntu 24.04 LTS server with sudo access
  • A domain such as grafana.example.com
  • DNS A or AAAA record pointing that hostname to the server
  • Docker Engine and Docker Compose v2
  • Nginx and Certbot for HTTPS
  • curl, jq, and openssl for checks and secret generation
  • At least one data source to connect later, such as Prometheus

Security Notes:

  • Do not expose Grafana's container port directly to the internet. Bind it to 127.0.0.1 and expose only Nginx over ports 80 and 443.
  • Generate a unique admin password before first start and store it in a password manager.
  • Disable public sign-up unless you intentionally run an open community instance.
  • Treat provisioning files as infrastructure code. Do not commit API keys, passwords, or cloud credentials to a public repository.
  • Back up the Grafana data volume and deployment files together so dashboards, users, alerting configuration, and plugins can be restored.

Start with an updated host and a restrictive firewall:

sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg jq openssl ufw nginx certbot python3-certbot-nginx

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Installation Guide

This deployment keeps all working files under /opt/grafana. Docker Compose runs Grafana, Nginx handles HTTPS, and a named Docker volume stores persistent application data.

1. Install Docker Engine

Install Docker and confirm the Compose plugin is available:

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
newgrp docker

docker --version
docker compose version

If the Docker group is not active in your current shell, log out and back in before continuing.

2. Create the Project Directory

Create a private project directory with folders for provisioning, dashboards, and local backup staging:

sudo mkdir -p /opt/grafana/{provisioning/datasources,provisioning/dashboards,dashboards,backups}
sudo chown -R "$USER":"$USER" /opt/grafana
chmod 700 /opt/grafana
chmod 700 /opt/grafana/backups
cd /opt/grafana

Keep this directory private because .env contains the first admin password and may later contain data source credentials.

3. Generate Secrets and Create .env

Generate a strong initial admin password and write the environment file:

cd /opt/grafana

GRAFANA_ADMIN_PASSWORD_VALUE="$(openssl rand -base64 36 | tr -d '\n')"

cat > .env <<EOF
GF_SERVER_DOMAIN=grafana.example.com
GF_SERVER_ROOT_URL=https://grafana.example.com/
GF_SECURITY_ADMIN_USER=admin
GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD_VALUE}
GF_USERS_ALLOW_SIGN_UP=false
GF_AUTH_ANONYMOUS_ENABLED=false
GF_ANALYTICS_REPORTING_ENABLED=false
GF_LOG_LEVEL=info
EOF

chmod 600 .env
printf 'Initial Grafana admin password: %s\n' "${GRAFANA_ADMIN_PASSWORD_VALUE}"

Save the generated password in a password manager. Grafana uses these admin credentials during initial setup. After users exist, change admin credentials from Grafana itself or use the Grafana CLI inside the container.

4. Create compose.yaml

Create a Compose file for the Grafana Open Source image:

services:
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    env_file: ./.env
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
      - ./provisioning:/etc/grafana/provisioning:ro
      - ./dashboards:/var/lib/grafana/dashboards:ro
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost:3000/api/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5

volumes:
  grafana_data:
    name: grafana_data

The official Grafana documentation also documents grafana/grafana-enterprise, which is free to run and includes the OSS feature set plus optional enterprise capabilities. This guide uses grafana/grafana because it is the open-source image and is enough for a private monitoring dashboard.

 Grafana Docker reverse proxy stack

Caption: Nginx publishes the HTTPS site while the Grafana container stays bound to localhost with persistent data in a named Docker volume.

5. Start Grafana

Validate the Compose file, pull the image, and start the container:

cd /opt/grafana
docker compose config
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --since=2m grafana

Test the local health endpoint:

curl -s http://127.0.0.1:3000/api/health | jq

A healthy response includes a database status and Grafana version information. If the health check fails, inspect docker compose logs grafana before configuring the public proxy.

6. Configure Nginx and HTTPS

Create /etc/nginx/sites-available/grafana.example.com:

server {
    listen 80;
    listen [::]:80;
    server_name grafana.example.com;

    location / {
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:3000;
    }
}

Enable the site and request a certificate:

sudo ln -s /etc/nginx/sites-available/grafana.example.com /etc/nginx/sites-enabled/grafana.example.com
sudo nginx -t
sudo systemctl reload nginx

sudo certbot --nginx -d grafana.example.com
sudo systemctl reload nginx

Now open https://grafana.example.com in your browser and sign in with the generated admin password.

Configuration

Grafana's Docker image is configured through environment variables and files mounted into the container. The most important settings in this guide are:

  • GF_SERVER_ROOT_URL: the public HTTPS URL users open in the browser.
  • GF_SECURITY_ADMIN_USER and GF_SECURITY_ADMIN_PASSWORD: the initial admin credentials.
  • GF_USERS_ALLOW_SIGN_UP=false: prevents visitors from creating accounts without approval.
  • GF_AUTH_ANONYMOUS_ENABLED=false: keeps dashboards private unless you explicitly enable public sharing.
  • /var/lib/grafana: persistent application data, stored in the grafana_data volume.
  • /etc/grafana/provisioning: file-based data source and dashboard provisioning.

Provision a Prometheus Data Source

Grafana can provision data sources from YAML. If your Prometheus server is reachable from the Grafana container, create provisioning/datasources/prometheus.yaml:

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus.example.internal:9090
    isDefault: true
    editable: false

Restart Grafana after changing provisioning files:

cd /opt/grafana
docker compose restart grafana
docker compose logs --since=1m grafana

If Prometheus runs in another Compose project, place both services on a shared Docker network or point url to a private DNS name that the Grafana container can resolve.

Provision Dashboard Files

Create a dashboard provider in provisioning/dashboards/default.yaml:

apiVersion: 1

providers:
  - name: Local dashboards
    orgId: 1
    folder: Operations
    type: file
    disableDeletion: false
    updateIntervalSeconds: 30
    allowUiUpdates: false
    options:
      path: /var/lib/grafana/dashboards
      foldersFromFilesStructure: true

Place exported dashboard JSON files under /opt/grafana/dashboards. Grafana will load them into the Operations folder. This pattern lets you review dashboard changes in Git before applying them to production.

 Grafana provisioning workflow

Caption: Provisioning files let operators manage data sources and dashboard providers as repeatable configuration instead of manual click paths.

Usage

After the first login, work through a small operational checklist:

  1. Open Administration > Users and access and create named accounts for real operators.
  2. Change the initial admin password after confirming you can log in.
  3. Add your first data source from Connections > Data sources, or use the provisioning file above.
  4. Create a folder such as Operations, Applications, or Homelab to keep dashboards organized.
  5. Import one well-known dashboard for your data source, then customize it instead of accepting every default panel.
  6. Create a simple alert rule only after the dashboard panels show trustworthy data.
  7. Test notification delivery before relying on alerts overnight.

For a first dashboard, start with host-level metrics: CPU usage, memory usage, disk space, filesystem read and write rate, network throughput, and container restarts. Add annotations for deployments later. A small dashboard that answers common operational questions is better than a huge dashboard nobody reads during an incident.

If you need to reset the admin password from the server, run:

cd /opt/grafana
NEW_ADMIN_PASSWORD="$(openssl rand -base64 36 | tr -d '\n')"
docker compose exec grafana grafana cli admin reset-admin-password "${NEW_ADMIN_PASSWORD}"
printf 'New Grafana admin password: %s\n' "${NEW_ADMIN_PASSWORD}"

Store the new value in your password manager immediately.

Screenshots and Visuals

The diagrams in this guide are original deployment visuals, not scraped product screenshots. They show the recommended network boundary, provisioning flow, and maintenance loop for a small self-hosted Grafana service.

 Grafana backup and maintenance loop

Caption: Keep Grafana reliable by pairing volume backups with configuration backups, restore tests, update reviews, and alert checks.

Troubleshooting

  • Browser shows a bad gateway from Nginx: Run docker compose ps and confirm Grafana is healthy. Then check that Nginx proxies to http://127.0.0.1:3000.
  • Login uses the old password after editing .env: The initial admin password is applied when the admin user is created. Reset the password from Grafana or use grafana cli admin reset-admin-password.
  • Dashboard panels say no data: Open the data source settings and use Save & test. Confirm the Grafana container can resolve and reach the data source URL.
  • Provisioned dashboard edits disappear: allowUiUpdates: false means file-backed dashboards should be changed in JSON files, not permanently edited in the UI.
  • HTTPS callback or links use the wrong host: Check GF_SERVER_ROOT_URL, GF_SERVER_DOMAIN, and the Nginx X-Forwarded-Proto header.
  • Plugins disappear after recreating the container: Plugins live under /var/lib/grafana/plugins, which is inside the grafana_data volume. Confirm the volume is mounted and backed up.
  • Container cannot write to storage: Inspect docker compose logs grafana for permission errors and confirm the named volume is attached to /var/lib/grafana.

Scaling, Securing, and Next Steps

For a small deployment, a single Grafana container with a named volume is simple and reliable. As usage grows, consider moving the Grafana database from the embedded SQLite file to PostgreSQL, running Prometheus and Loki on separate storage, placing Grafana behind SSO, and managing dashboards through Git review. Teams with strict access requirements should define organizations, folders, teams, and permissions carefully instead of sharing one admin account.

Back up both configuration and data. The deployment files explain how Grafana starts, while the volume contains the internal database, plugins, sessions, alerting state, and UI-created dashboards. A practical backup routine looks like this:

cd /opt/grafana
mkdir -p backups

tar czf "backups/grafana-config-$(date +%F).tgz" \
  compose.yaml .env provisioning dashboards

docker run --rm \
  -v grafana_data:/data:ro \
  -v "$PWD/backups:/backup" \
  alpine sh -c 'cd /data && tar czf /backup/grafana-data-$(date +%F).tgz .'

ls -lh backups

Copy the resulting archives to encrypted off-server storage. To restore on a replacement host, install Docker, recreate /opt/grafana, unpack the configuration archive, recreate the grafana_data volume, unpack the data archive into that volume with a temporary container, and then run docker compose up -d.

For updates, read Grafana release notes before pulling a new image. Then run:

cd /opt/grafana
docker compose pull
docker compose up -d
docker compose logs --since=2m grafana
curl -s http://127.0.0.1:3000/api/health | jq

By following this guide, you end with a private Grafana instance available over HTTPS, protected behind Nginx, backed by persistent storage, ready for provisioned data sources, and prepared for operational backups. The next useful step is to connect Prometheus or another trusted data source, build a small service-health dashboard, and test one alert from trigger to notification.

References

Share:

Get new posts in your inbox

No spam. One short email per new article — practical PHP, Laravel, devops, and AI-assisted workflows.

Comments

Powered by GitHub Discussions via Giscus. A free GitHub account is required.