Skip to content

BezaluLLC/Grafana-Backup

Repository files navigation

Grafana Dashboard Backup

A serverless Azure Function (PowerShell, Flex Consumption) that runs on a daily cron schedule, exports all dashboard JSON from an Azure Managed Grafana instance, and commits them to a GitHub repository as an incremental backup.

How it works

  1. Timer trigger fires daily at midnight UTC (0 0 0 * * *).
  2. The function queries the Grafana API for every dashboard.
  3. Each dashboard's JSON is exported with id nulled out so it can be directly imported into a fresh Grafana instance via POST /api/dashboards/db.
  4. The file tree in the target GitHub branch is compared (by Git blob SHA) with the new exports.
  5. If any dashboards were added, modified, or removed, a single batch commit is created on the target branch. The commit message follows this format:
    chore(backup): Backup on 2026-03-19
    
    Added: My New Dashboard (uid123)
    Modified: Server Metrics (uid456)
    Removed: dashboards/OldFolder/uid789.json
    
  6. If nothing changed, no commit is made.

Repository layout

Dashboards are stored mirroring the Grafana folder structure:

dashboards/
├── General/
│   └── abc123.json
├── Infrastructure/
│   └── def456.json
└── Application/
    └── ghi789.json

Each file is named by the dashboard's Grafana UID. The dashboard title, panels, and all configuration are inside the JSON.


Prerequisites

Requirement Details
Azure Function App Flex Consumption plan, PowerShell 7.4 runtime, North Central US (or your region)
Grafana Service Account Token Create a Service Account in your Azure Managed Grafana instance with Viewer role, then generate a token. See Grafana Service Accounts docs.
GitHub PAT Classic PAT with repo scope, or a fine-grained PAT with Contents: Read and write permission on the target repository.
GitHub repository Private repo with a branch named grafana_export already created. The branch can be empty (initial commit only) or have existing content.

Environment variables

Configure these as Application Settings on the Function App (or in local.settings.json for local dev):

Variable Required Description
GRAFANA_URL Yes Azure Managed Grafana endpoint, e.g. https://myinstance.cuse.grafana.azure.com
GRAFANA_API_KEY Yes Grafana Service Account token (starts with glsa_)
GITHUB_REPO Yes Target repo in owner/repo format
GITHUB_PAT Yes GitHub Personal Access Token
GITHUB_BRANCH No Branch to commit to (default: grafana_export)
GITHUB_COMMIT_AUTHOR_NAME No Git author name (default: Grafana Backup Bot)
GITHUB_COMMIT_AUTHOR_EMAIL No Git author email (default: grafana-backup-bot@users.noreply.github.com)

Deploy via VS Code

  1. Install the Azure Functions VS Code extension.
  2. Copy local.settings.json.example to local.settings.json and fill in your values (for local testing only).
  3. Right-click the workspace root in VS Code → Deploy to Function App… → select your Flex Consumption Function App.
  4. After deployment, add the environment variables above as Application Settings in the Azure portal (or via Azure CLI):
    az functionapp config appsettings set \
      --name <function-app-name> \
      --resource-group <rg-name> \
      --settings \
        GRAFANA_URL="https://..." \
        GRAFANA_API_KEY="glsa_..." \
        GITHUB_REPO="owner/repo" \
        GITHUB_PAT="ghp_..."

Local development

# Install Azure Functions Core Tools (if not already)
npm install -g azure-functions-core-tools@4 --unsafe-perm true

# Copy and fill in settings
Copy-Item local.settings.json.example local.settings.json
# Edit local.settings.json with real values

# Start the function locally
func start

The timer trigger won't fire immediately when running locally. To test on demand, send an admin request:

Invoke-RestMethod -Uri "http://localhost:7071/admin/functions/BackupGrafanaDashboards" `
    -Method Post -Body '{}' -ContentType "application/json"

Restoring dashboards to a fresh Grafana instance

Each JSON file under dashboards/ is a complete Grafana dashboard model (with id: null) that can be imported directly:

$token = "glsa_your_new_instance_token"
$grafanaUrl = "https://new-instance.grafana.azure.com"

Get-ChildItem -Path ./dashboards -Recurse -Filter *.json | ForEach-Object {
    $dashboard = Get-Content $_.FullName -Raw | ConvertFrom-Json
    $folderName = $_.Directory.Name

    $body = @{
        dashboard = $dashboard
        overwrite = $true
        message   = "Restored from backup"
    } | ConvertTo-Json -Depth 100

    Invoke-RestMethod -Uri "$grafanaUrl/api/dashboards/db" `
        -Headers @{ "Authorization" = "Bearer $token"; "Content-Type" = "application/json" } `
        -Method Post -Body $body
}

Note: This does not recreate Grafana folders automatically. Create the target folders in the new instance first, or extend the script to call POST /api/folders before importing.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors