What is Grafana?
Grafana is the open source standard for dashboards and observability. It connects to data sources such as Prometheus, Loki, InfluxDB, PostgreSQL, MySQL and Elasticsearch, and turns their data into dashboards, alerts and reports. Homelab users run it to graph server load, network traffic, Home Assistant sensors and power use. Grafana is open source under the AGPL-3.0 license.
Requirements
- A Linux server with Docker Engine and Docker Compose v2. Grafana alone is light: 1 vCPU and 512 MB of RAM are enough to start.
- At least one data source. If you have none yet, follow our Prometheus guide to collect server metrics first.
- A domain name if you want HTTPS access.
Step 1: Prepare the server
This guide assumes Ubuntu 24.04 with Docker installed from the official Docker Engine guide.
mkdir -p ~/grafana && cd ~/grafana
Step 2: Create the Docker Compose file
Save this as docker-compose.yml:
services:
grafana:
image: grafana/grafana:13.2.3
container_name: grafana
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
GF_SERVER_ROOT_URL: ${GF_SERVER_ROOT_URL}
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: ${GF_ADMIN_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: "false"
GF_ANALYTICS_REPORTING_ENABLED: "false"
GF_PLUGINS_PREINSTALL: grafana-clock-panel
volumes:
- grafana_storage:/var/lib/grafana
volumes:
grafana_storage: {}
Then create .env next to it:
GF_SERVER_ROOT_URL=https://grafana.example.com/
GF_ADMIN_PASSWORD=CHANGE_ME
Replace CHANGE_ME with the output of openssl rand -hex 32. The admin password variable only applies when the database is first created; after that, change it in the UI. Pinning 13.2.3 means upgrades only happen when you edit the tag.
Grafana's data lives in a named volume, which avoids permission problems: the container runs as user ID 472, so a bind-mounted folder must be owned by that user (sudo chown -R 472:0 ./data). If you want to keep a stack with metrics too, add Grafana to the same Compose file as Prometheus so it can reach http://prometheus:9090 by service name.
Step 3: Start and open the app
docker compose up -d
docker compose logs -f grafana
The port is bound to localhost, so either finish Step 4 first or use an SSH tunnel for a quick look: ssh -L 3000:127.0.0.1:3000 user@YOUR_SERVER_IP, then open http://localhost:3000. Sign in as admin with the password from .env.
Go to Connections, Data sources, Add data source, pick Prometheus (or your source), enter its URL and click Save & test. Then import a ready-made dashboard: Dashboards, New, Import, and enter dashboard ID 1860 (Node Exporter Full) from grafana.com.
Step 4: Put it behind HTTPS
With Caddy on the host:
grafana.example.com {
reverse_proxy 127.0.0.1:3000
}
Caddy gets a Let's Encrypt certificate automatically and passes WebSockets, which Grafana Live uses. Make sure GF_SERVER_ROOT_URL matches the public URL, or login redirects and links in alert notifications point to the wrong place. The localhost binding matters: Docker-published ports bypass ufw, so "3000:3000" would expose plain HTTP even with the firewall enabled.
Backups and upgrades
Everything Grafana stores (dashboards, users, data source settings, alert rules) is in the SQLite database grafana.db inside the volume. Back it up with:
docker compose stop grafana
docker run --rm -v grafana_grafana_storage:/data -v "$PWD":/backup alpine \
tar czf /backup/grafana-$(date +%F).tgz -C /data .
docker compose start grafana
For an extra safety net, keep important dashboards as JSON in Git (Dashboard, Export). To upgrade, change the tag to the new version, read the "What's new" and breaking changes notes for major releases, then:
docker compose pull && docker compose up -d
Troubleshooting
- "GF_PATHS_DATA is not writable": a bind mount is owned by the wrong user. Run
sudo chown -R 472:0on the folder or use a named volume. - Admin password from .env does not work: the database already existed when you set it. Reset it with
docker compose exec grafana grafana cli admin reset-admin-password NEW_PASSWORD. - "Origin not allowed" or redirect loops behind the proxy:
GF_SERVER_ROOT_URLdoes not match the domain in the browser. - Data source test fails with "connection refused": inside the container,
localhostis Grafana itself. Use the service name or the host's LAN IP.
Next steps
Set up alerting with a contact point such as email or Slack, add Loki for logs, provision data sources and dashboards from files so the setup is reproducible, and enable single sign-on through Authentik or another OAuth provider.
Spotted something out of date? Tell us and we will update the guide.