Run Grafana with Docker Compose and a Provisioning Folder
Start Grafana from a compose file with all configuration mounted as files.
This guide runs Grafana and a Prometheus data source locally using Docker Compose, with every setting supplied through files and environment variables. You never open the UI to configure anything. You need Docker with the Compose plugin.
Lay out the project
grafana-as-code/
├── compose.yaml
├── .env # secrets and versions, not committed
├── grafana/
│ ├── grafana.ini
│ └── provisioning/
│ ├── datasources/
│ │ └── prometheus.yaml
│ ├── dashboards/
│ │ └── providers.yaml
│ ├── alerting/ # added in the alerting guide
│ └── plugins/
├── dashboards/
│ └── overview.json
└── prometheus/
└── prometheus.yml
Keep this in Git. Add .env to .gitignore and commit a .env.example instead.
The compose file
compose.yaml:
services:
grafana:
image: grafana/grafana:13.1.0 # pin a version you have tested
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- ./grafana/grafana.ini:/etc/grafana/grafana.ini:ro
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./dashboards:/var/lib/grafana/dashboards:ro
- grafana-data:/var/lib/grafana
depends_on:
- prometheus
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
volumes:
grafana-data:
The important mounts:
/etc/grafana/grafana.iniis the server configuration file inside the official image./etc/grafana/provisioningis where Grafana looks fordatasources,dashboards,alerting, andpluginsfolders./var/lib/grafana/dashboardsis an arbitrary folder for dashboard JSON. The path must match what you set in the dashboard provider below.grafana-datais a named volume for Grafana’s SQLite database and plugins. Without it, state is lost when the container is recreated.
The config mounts are read-only on purpose: Grafana doesn’t need to write to them.
.env:
GRAFANA_ADMIN_PASSWORD=change-me-before-sharing
Note that GF_SECURITY_ADMIN_PASSWORD is only applied the first time Grafana creates its database. Changing it later in the compose file has no effect on an existing volume. Remove the volume to start over, or reset the password with the CLI.
A minimal grafana.ini
[server]
http_port = 3000
[analytics]
reporting_enabled = false
check_for_updates = false
[log]
mode = console
level = info
[auth]
disable_login_form = false
Anything you leave out falls back to Grafana’s defaults. The next guide covers the full rules for grafana.ini and GF_* overrides.
Provision a data source
grafana/provisioning/datasources/prometheus.yaml:
apiVersion: 1
datasources:
- name: Prometheus
uid: prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
The uid is what dashboards and alert rules should reference, so pick a stable one. The next guides explain every field.
Provision a dashboard provider and one dashboard
grafana/provisioning/dashboards/providers.yaml:
apiVersion: 1
providers:
- name: default
type: file
updateIntervalSeconds: 30
allowUiUpdates: false
options:
path: /var/lib/grafana/dashboards
dashboards/overview.json (a minimal dashboard; see the dashboards guide for the details):
{
"uid": "overview",
"title": "Overview",
"schemaVersion": 39,
"panels": [
{
"id": 1,
"type": "timeseries",
"title": "Targets up",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"targets": [
{ "refId": "A", "expr": "up", "datasource": { "type": "prometheus", "uid": "prometheus" } }
]
}
]
}
And a tiny prometheus/prometheus.yml so there is data to show:
global:
scrape_interval: 15s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
Start it and check without the UI
docker compose up -d
docker compose logs grafana | grep -i provisioning
curl -s http://localhost:3000/api/health
curl -s -u admin:$GRAFANA_ADMIN_PASSWORD http://localhost:3000/api/datasources | jq '.[].name'
curl -s -u admin:$GRAFANA_ADMIN_PASSWORD "http://localhost:3000/api/search?type=dash-db" | jq '.[].title'
The health endpoint should report ok, the data source list should contain Prometheus, and the search should return Overview. Open http://localhost:3000 if you want to see the result, but nothing was clicked to get here.
Change something and watch it apply
- Edit
dashboards/overview.json. WithinupdateIntervalSecondsGrafana picks up the change without a restart. - Edit the data source file and restart:
docker compose restart grafana. Data sources are read at startup. - Delete a dashboard file. The dashboard is removed unless the provider sets
disableDeletion: true.
Install plugins from configuration
Plugins can be installed at container start by environment variable, so they stay in the compose file:
environment:
GF_PLUGINS_PREINSTALL: grafana-clock-panel
Older versions use GF_INSTALL_PLUGINS instead. Check the documentation for your version, and note that provisioning only configures plugins that are already installed.
Clean up
docker compose down # keep the data volume
docker compose down -v # also delete Grafana's database Popular Guides
-
Operate Grafana as Code: The Big Picture What can live in files, what needs an API, and how the pieces fit together. -
Configure the Grafana Server with grafana.ini and Environment Variables Know where config files live, how overrides are ordered, and how to keep settings in Git. -
Provision Dashboards from JSON Files Load dashboards from disk, map folders from directories, and author them without losing changes. -
Provision Alert Rules, Contact Points, and Notification Policies Define the whole alerting pipeline in YAML so it is reviewed and versioned like code. -
Deploy Grafana on Kubernetes with Helm and Provisioning Values Run Grafana from a Helm chart where data sources, dashboards, and settings all come from values files. -
Reload, Verify, and Troubleshoot Provisioning from the Command Line Find out what Grafana loaded, reload without a restart, and fix the usual provisioning errors.