ObservabilityXXIII · Grafana FoundationsGrafanaFoundations
Grafana Anatomy
What you'll learn
- Describe the major Grafana server subsystems: HTTP front end, query engine, plugin runtime, and storage layer
- Locate the configuration, database, and provisioning files on a containerised Grafana 11 instance
- Read the /api/health endpoint to distinguish a healthy instance from one that has only partially started
- Choose between sqlite and Postgres / MySQL based on availability and scale requirements
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
A user opens Grafana at 09:00. Three dashboard tabs load. Each panel renders in under a second. To the user, Grafana is “the dashboard page”. To the operator, Grafana is a single Go binary holding five sub-systems, a database, and an unbounded set of plugins, any one of which can be the reason a panel is slow or empty.
This lesson opens the hood. The goal is not to teach every option key. The goal is to name the pieces so that the next three lessons (datasources, panels, provisioning) attach to a known shape.
What Grafana is
Grafana is a single-server application written in Go. It serves a single-page web UI, accepts arbitrary data-source plugins over a stable plugin protocol, queries those plugins on the operator’s behalf, renders the returned frames into panels, and persists dashboards, alert rules, users, and audit data in a SQL database.
Grafana is not a data store. It does not own Prometheus metrics, Loki logs, or Tempo traces. It owns the views of those things. This distinction is the single most common source of operational confusion: a missing dashboard is almost never a Grafana outage; it is almost always a missing or unreachable data source.
Why a sysadmin cares
Three operational pain points disappear once you understand the anatomy.
- “Grafana is broken” tickets. Most arrive because a panel shows no data, not because the web UI failed. Knowing where the failure can live (HTTP, plugin, proxy, storage) shortens the diagnostic from hours to minutes.
- Capacity arguments. Disk used by the
grafana.dbfile. CPU spent in plugin health checks. Memory held by panel render cache. These three numbers answer the “how big should this host be” question honestly, which means knowing which subsystem owns each one. - Upgrade planning. Storage migrations, plugin-breaking changes, and licence-mode switches each map to a specific subsystem. A targeted upgrade plan starts there.
How it works
The mental model has five layers. Each layer has its own configuration, its own failure modes, and its own monitoring signal.
+-----------------------------------------------------------------+
| Web UI (React SPA) |
| Dashboards | Explore | Alerting | Connections | Admin |
+-----------------------------------------------------------------+
|
v
+-----------------------------------------------------------------+
| HTTP server (Go net/http) |
| /api/health /api/datasources /api/folders /api/... |
+-----------------------------------------------------------------+
|
+---------------------+----------------------+
| | |
v v v
+---------------+ +-------------------+ +------------------+
| Query engine | | Alert scheduler | | Provisioning |
| (frontend | | (rule evaluator, | | loader (yaml |
| data frames) | | contact points, | | polling on |
| | | silences) | | /etc/grafana) |
+---------------+ +-------------------+ +------------------+
|
v
+-----------------------------------------------------------------+
| Plugin runtime (backend + frontend) |
| prometheus | loki | tempo | mysql | postgres | influx | ... |
+-----------------------------------------------------------------+
|
v
+-----------------------------------------------------------------+
| Storage layer |
| sqlite (file) | postgres | mysql |
| dashboards, users, orgs, alert rules, audit log |
+-----------------------------------------------------------------+
The query engine is the part operators interact with most. It
receives a query, dispatches it to the relevant data-source plugin
(over the plugin’s QueryData method), receives a data frame back
(a columnar, typed structure that has replaced the older TimeSeries
type in Grafana 11), and applies the panel’s transformations before
the rendering pipeline turns it into pixels.
How to configure the server
The Grafana configuration block below is a realistic starting point
for a small production deployment. Everything outside the
[paths], [database], and [security] sections has sensible
defaults; changing them without reason is how incidents start.
# /etc/grafana/grafana.ini
[paths]
data = /var/lib/grafana
logs = /var/log/grafana
plugins = /var/lib/grafana/plugins
provisioning = /etc/grafana/provisioning
[server]
http_port = 3000
domain = grafana.internal.example
enforce_domain = true
root_url = https://grafana.internal.example/
[security]
admin_user = grafana-admin
secret_key = a-long-random-string-from-secrets-manager
disable_gravatar = true
cookie_secure = true
cookie_samesite = lax
[database]
# sqlite is the default. Postgres / MySQL are documented options
# for HA. The url format follows the underlying sql driver.
type = sqlite3
path = grafana.db
[users]
allow_sign_up = false
auto_assign_org_role = Viewer
[log]
level = info
mode = console
# Mandatory for alerting.
[unified_alerting]
enabled = true
# Image rendering is a separate container in production.
[rendering]
server_url = http://grafana-image-renderer:8081/render
callbacks = http://grafana:3000/
For a high-availability pair, swap [database] for a managed
Postgres and add a [session] block pointing at Redis. The
[database] and [session] blocks are the only two that must
match across all nodes in the cluster; everything else can drift
freely without harm.
How to validate it
A fresh Grafana should pass three checks in order. The first proves the process is up. The second proves the database is reachable. The third proves the API is answering authenticated requests.
Severity: READ-ONLY. All commands below inspect; none change state.
# 1. Process is up and listening.
curl -sf -o /dev/null -w "%{http_code}\n" http://grafana:3000/login
# 3000
# 2. Health endpoint distinguishes "up" from "ready".
curl -s http://grafana:3000/api/health | jq
{
"version": "11.1.0",
"commit": "abc1234",
"database": "ok",
"datasources": {
"Prometheus": {
"status": 1,
"message": "Data source is working"
}
},
"unifiedAlerting": {
"status": 1,
"message": "ok"
}
}
The four sub-statuses that matter:
databasemust be"ok". Anything else means Grafana cannot read its own state.- each data source’s
statusmust be1.0is unreachable. unifiedAlerting.statusmust be1if alerting is enabled.versionis the running build — confirm it matches the one deployed.
# 3. Authenticated API answers. Use a service account token in
# real use; the basic-auth form is for diagnosis only.
curl -sf -u "grafana-admin:$GF_ADMIN_PASSWORD" \
http://grafana:3000/api/org | jq
{
"id": 1,
"name": "Main Org.",
"address": { "address1": "", "address2": "", "city": "", ... },
"created": "2026-08-01T00:00:00Z"
}
A 401 here against a known-good credential indicates the cookie /
token signing key (secret_key) has changed between the previous
session and now — sessions are invalidated and must be re-issued.
How it can fail
The five most common failure shapes, with their observable symptoms:
- Database is not reachable. Symptom:
curl http://grafana:3000/api/healthreturns"database": "ok"or"database": "failing". The login page renders but/api/*returns500. Logs containdatabase is not healthy. - Data source is unreachable but Grafana is fine. Symptom:
api/healthreturns"database": "ok"and the data source’s status is0. Panels display “No data” or the red triangle state. The login page, dashboards listing, and admin pages all load — only panels are broken. This is the operationally dangerous case: the page loads, the user clicks a panel, and nothing useful is shown. - Plugin is missing. Symptom: dashboard panel shows
“Plugin not found” or the panel error of an unspecified kind.
Logged at start:
Failed to load plugin: <id>. Caused by removing the plugin directory or by a version mismatch between Grafana and the plugin manifest. secret_keyrotated unintentionally. Symptom: every authenticated session returns401. TheOIDCandLDAPsign-in paths break. Operators are signed out and cannot sign back in if the IdP flow uses the same key. This is the failure caused by redeploying with a freshsecret_keyrather than restoring it from secrets management.- Disk full on
/var/lib/grafana. Symptom: write paths (dashboard save, alerting silences, audit log writes) return500. Reads continue to work. The sqlite file may corrupt on a hard restart. Diagnose withdu -sh /var/lib/grafana/*. - Image render service is unreachable. Symptom: PDF / PNG
exports hang or return
502. The dashboard itself renders fine in the browser. Check therendering_serviceenv var points at a reachable sibling.
How to troubleshoot it
The diagnostic order is reproducible across the failure shapes above:
- Process up?
systemctl status grafana-server(ordocker psfor a container). If not up, the host or container runtime is the fault. Read stdout/stderr. Restart only after the cause is found. - API up?
curl -sf http://grafana:3000/api/health. Returns200for “process is up”. Read the JSON body fordatabase,unifiedAlerting, and per-data-source status. This single call resolves three of the six common failures above. - Database reachable?
database: okfrom the call above. If not, reach the database directly. For sqlite:sqlite3 /var/lib/grafana/grafana.db ".schema". For Postgres:psql -h $PGHOST -U grafana -c "select 1". - Plugin load?
grafana-cli plugins ls(binary) orls /var/lib/grafana/plugins/(container). For each plugin, inspect the manifest at<plugin-dir>/<plugin>/plugin.jsonand confirmversionmatches the runtime requirement of Grafana 11. - Disk pressure?
du -sh /var/lib/grafana/*thendf -h /var/lib/grafana. Thepngdirectory is the render cache; it is safe to truncate when full. - Network to data sources? From the Grafana host,
curl -sf -o /dev/null -w "%\{http_code\}\n" http://prometheus:9090/-/ready. If this fails the proxy is the source. If it succeeds, the data source plugin is the source.
Security implications
Grafana’s attack surface is the HTTP listener. Treat it as an internet-facing service. The relevant controls:
secret_keymust be 32+ random bytes, stored in a secrets manager, and never randomised between restarts. Rotating it silently invalidates every session and breaks SSO token decryption.admin_userandadmin_passwordmust be set from environment variables, not literals, on first start; afterwards theadmin_passwordline ingrafana.iniis ineffective. Reset through the API or thegrafana-cli admin reset-admin-passwordcommand.- Authentication must be brought forward of any internet
ingress —
auth.proxyor anOIDCprovider in front ofGrafanais the correct control;basic_authagainst the public listener is not. - Data source
secureJsonDatamust not contain long-lived credentials for sources that support better auth. For Prometheus, basic auth with a read-only token is appropriate. For Postgres, a read-only data-source user is appropriate. For Loki with multi-tenant mode, theX-Scope-OrgIDheader must be set in the data source configuration rather than carried in the URL. disable_gravatar = trueandcookie_samesite = laxare small defaults worth setting.
The full security posture of Grafana is out of scope for this lesson and lives in Part XXII.
Performance implications
Three limits dominate in production:
- Render cache memory. Each rendered PNG for a heavy dashboard
can be a few hundred KiB. Bound the cache size; rotate on
schedule. The default
cache_sizeof100is a starting point, not a guarantee. - Alert scheduler CPU. Every rule evaluates every interval.
A thousand rules at
evaluation_interval = 60sis roughly one evaluation every 60 ms across the population. This is cheap on modern hardware but the rule count is the lever an operator holds. - Provision polling. Each provisioning path polls its directory at a configured interval. The default is 60 seconds; this is harmless on disk-resident YAML but can become the bottleneck if the path is a slow remote mount.
Scale out before scale up. A second Grafana node looking at the same Postgres handles three times the load for a quarter of the hardware cost.
Production guidance
- Pin the Grafana version and the plugin versions in the same release pipeline. Plugin and Grafana version mismatches are the second-most-common reason dashboards fail to load.
- Set
secret_keyfrom a secrets manager and read it on every start, not at provisioning time. Helm and Compose both have the right primitives for this. - Treat
/api/healthas a load-balancer health check only whendatabase: okis the response. Don’t fall back to the login page as a health check; it lies. - Provision data sources and dashboards from the filesystem, not the UI, as soon as the team has more than one operator. File-based provisioning is the topic of Part XXIII lesson 6.
- Set the
image_renderer_urlto a sibling service and not the in-process queue once a single Grafana host handles more than ~20 dashboards in a PDF / PNG cycle.
Verification
You should now be able to answer:
- Where is the Grafana database on a default container install, and what does it own?
- What does a healthy
/api/healthreturn and how do you read it? - Why is “the panel shows no data” almost never a Grafana outage?
- Which configuration knob gates whether a second Grafana node can join the first as a peer?
- Where in the file system does file-based provisioning live?
Quiz
Knowledge check · 8 questions
Q1. What single binary does Grafana ship as?
Q2. Which subsystem of Grafana owns dashboards, alert rules, and the audit log?
Q3. A red triangle on a panel almost always means Grafana itself is down.
Q4. Which key gates the ability to run a high-availability Grafana pair?
Q5. Name one field in the /api/health JSON body that confirms the database is reachable.
Q6. Which of the following subsystems can hold panel-rendering state in a running Grafana?
Q7. What is the right diagnostic first step when a dashboard panel shows no data?
Q8. Which behaviour suggests the secret_key was rotated?
Passing score: 75%. Answers are checked in this browser.