Grafana Grafana

Run Grafana with Docker Compose and a Provisioning Folder

Start Grafana from a compose file with all configuration mounted as files.

10 min Beginner

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.ini is the server configuration file inside the official image.
  • /etc/grafana/provisioning is where Grafana looks for datasources, dashboards, alerting, and plugins folders.
  • /var/lib/grafana/dashboards is an arbitrary folder for dashboard JSON. The path must match what you set in the dashboard provider below.
  • grafana-data is 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. Within updateIntervalSeconds Grafana 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