Infrastructure guide

Grafana Docker Compose: Persist Dashboards and Provision What Matters

Deploy a version-pinned Grafana container with persistent dashboards, reproducible provisioning, backups, and an upgrade path that protects state.

Published and reviewed by OpenAlt · September 25, 2026

A laptop showing an analytics dashboard with charts and graphs, symbolizing modern data analysis tools.
Photo by Negative Space on Pexels

TL;DR

A disposable Grafana container loses dashboards and configuration when it is removed. Mount /var/lib/grafana to a named Docker volume or bind mount because Grafana stores its embedded SQLite database, users, dashboards, plugins, and other data there.

Use file provisioning for configuration that should be reproducible, such as data sources and dashboard definitions. Use the volume for durable runtime state. Pin the Grafana image version, bootstrap the admin password from a secret file, back up the volume consistently, and never use default credentials as the operational state.

TOC

Persistence and provisioning

Persistence and provisioning solve different problems.

Persistence keeps Grafana’s runtime state between container replacements. Grafana listens on port 3000 and stores its embedded SQLite configuration, users, dashboards, and other data under /var/lib/grafana. Changes made only inside the container filesystem disappear when that container is deleted, so a volume or bind mount is required.

Provisioning makes selected configuration reproducible from files. It is ideal for data sources, dashboard providers, alerting configuration, and dashboard JSON that should be recreated consistently across environments.

A useful operating model is:

ConcernRecommended locationPurpose
Grafana database and runtime stateNamed volume at /var/lib/grafanaPreserve dashboards, users, settings, and plugins
Data sources/etc/grafana/provisioning/datasourcesRecreate connection definitions
Dashboard providers/etc/grafana/provisioning/dashboardsTell Grafana where dashboard files live
Dashboard JSONA provisioned dashboard directoryReproduce dashboards from version-controlled files
Admin passwordDocker secret fileKeep bootstrap credentials out of Compose YAML

Provisioning is not a replacement for backups. A provisioned dashboard may be reproducible, but the Grafana database still contains important operational state.

Version-pinned Docker Compose

Pin the image instead of using latest. The current stable Docker image listed by Grafana is 13.2.1, dated September 1, 2026.

services:
  grafana:
    image: grafana/grafana:13.2.1
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_USER: operator
      GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/grafana_admin_password
      GF_PATHS_PROVISIONING: /etc/grafana/provisioning
    secrets:
      - grafana_admin_password
    volumes:
      - grafana_data:/var/lib/grafana
      - ./provisioning:/etc/grafana/provisioning:ro
    restart: unless-stopped

secrets:
  grafana_admin_password:
    file: ./secrets/grafana_admin_password

volumes:
  grafana_data:

The file ./secrets/grafana_admin_password should contain the chosen administrator password and should not be committed to source control. The __FILE setting lets the container read that value from the mounted secret rather than exposing it in Compose.

This is an initial bootstrap mechanism. If Grafana has already initialized its database, changing the environment variable does not automatically rotate the existing administrator password. Change the password through Grafana’s supported user-management flow and update the secret for future rebuilds.

The named volume is managed by Docker and survives normal container replacement. Do not add -v to a teardown command unless you intentionally want to remove the stored Grafana database.

An IT professional operates a computer in a server room, managing network systems and connected devices.
Photo by panumas nikhomkhai on Pexels
Detailed image of illuminated server racks showcasing modern technology infrastructure.
Photo by panumas nikhomkhai on Pexels

Provision data sources and dashboards

Create a small provisioning tree beside the Compose file:

provisioning/
├── dashboards/
│   ├── providers.yaml
│   └── json/
└── datasources/
    └── prometheus.yaml

A data source definition can be concise:

apiVersion: 1

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

The URL assumes that another Compose service is named prometheus and listens on its normal internal port. Use the actual service name and endpoint in your stack. Credentials are intentionally not included; supply them through your environment’s secret-management approach when the data source requires authentication.

Define where dashboard JSON files are loaded:

apiVersion: 1

providers:
  - name: file-dashboards
    orgId: 1
    folder: ""
    type: file
    disableDeletion: true
    editable: false
    options:
      path: /etc/grafana/provisioning/dashboards/json

Place exported dashboard JSON files in provisioning/dashboards/json. Keep those files under version control if they represent the desired state of your monitoring stack.

editable: false protects the provisioned definition from casual UI changes. If operators must edit dashboards in Grafana, enable UI editing deliberately and decide which system is authoritative. Otherwise, a later provisioning update can make file-based configuration win over an uncommitted UI change.

The official Grafana provisioning documentation covers file-based data sources and dashboards in detail.

Verify the installation

Open http://localhost:3000 or the host address mapped to port 3000. Sign in with the administrator username and the password stored in the secret file.

Then verify three separate behaviors:

  1. The data source appears under Grafana’s data-source settings and passes its connection test.
  2. Provisioned dashboards load from the expected folder.
  3. Removing and recreating the container does not remove the dashboard database or users.

Check container logs if provisioning fails. A valid YAML file can still point to a path that is not mounted, a service name that is unreachable, or a dashboard directory containing invalid JSON.

Back up, upgrade, and roll back

Back up /var/lib/grafana consistently, not just the Compose file. For SQLite-backed Grafana, the safest simple approach is to stop Grafana, copy or snapshot the named volume, and start Grafana again. This prevents a live SQLite write from producing an inconsistent filesystem copy.

Also back up the provisioning directory and the administrator secret through your normal protected process. The provisioning files recreate selected configuration, while the volume backup preserves the database and runtime state.

An external MySQL or PostgreSQL database can separate Grafana’s database from the container and may be useful for larger or highly available deployments. It does not automatically back up plugins, provisioning files, or every file stored under /var/lib/grafana; those still need an explicit storage plan.

For upgrades, keep the image tag pinned, back up first, then change the tag in one controlled commit. Start the new container and verify login, data sources, dashboards, and plugins. For rollback, return to the previous tag only when its database schema is compatible. If the newer version performed a migration that cannot be reversed safely, restore the pre-upgrade database backup instead of relying only on an image downgrade.

Grafana’s official Docker installation documentation explains the container port and storage paths. The official download page lists available image versions.

Troubleshooting

If Grafana cannot write to the named volume, check the container’s filesystem permissions and whether the data mount is accidentally read-only. Named volumes avoid many host UID problems. Bind mounts require the host directory to be writable by the Grafana process.

If dashboards are missing, check that the provider file is under /etc/grafana/provisioning/dashboards, that its path matches the mounted directory, and that the JSON files are present inside the container. Also check orgId, folder settings, and startup logs.

If a data source is unavailable, test the service name from the Compose network. localhost inside the Grafana container means Grafana itself, not another container.

FAQ

Where does Grafana store dashboards?

By default, Grafana stores dashboard records in its embedded SQLite database under /var/lib/grafana. Provisioned dashboard JSON files live wherever your provider configuration points, commonly under /etc/grafana/provisioning/dashboards.

Is a volume enough for backups?

No. A volume preserves data between container replacements, but it is not an independent backup. Create consistent copies or snapshots of the volume, and back up provisioning files and any external database separately.

What is provisioning?

Provisioning is Grafana’s file-based configuration mechanism. YAML and JSON files define items such as data sources, dashboard providers, and dashboards so they can be recreated consistently.

Which port does Grafana use?

Grafana listens on port 3000 by default. A Compose mapping such as "3000:3000" exposes the container port on port 3000 of the Docker host.

Should you run the Grafana Enterprise image for free?

No, not for this basic persistence and provisioning use case. The Grafana OSS image is sufficient. Choose Enterprise when you specifically need its features and have the corresponding licensing or operational reason; persistence does not depend on the Enterprise image.

For related tooling, explore OpenAlt’s Grafana profile, BI & dashboards, and monitoring tools.