<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>CI/CD automation on Grafana Labs</title><link>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/</link><description>Recent content in CI/CD automation on Grafana Labs</description><generator>Hugo -- gohugo.io</generator><language>en</language><atom:link href="/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/index.xml" rel="self" type="application/rss+xml"/><item><title>CI/CD pipeline architecture</title><link>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/01-pipeline-architecture/</link><pubDate>Tue, 11 Aug 2026 10:11:48 +0200</pubDate><guid>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/01-pipeline-architecture/</guid><content><![CDATA[&lt;h2 id=&#34;cicd-pipeline-flow&#34;&gt;CI/CD pipeline flow&lt;/h2&gt;
&lt;p&gt;You can commit dashboard JSON by hand after every SDK change, but that still depends on someone remembering to regenerate and push. A CI/CD pipeline removes that bottleneck.&lt;/p&gt;
&lt;p&gt;This diagram shows one end-to-end flow from code change to a live dashboard when Git Sync is already connected to the repository.&lt;/p&gt;

&lt;div class=&#34;learning-hub-image&#34;&gt;
  &lt;a href=&#34;pipeline-flow.svg&#34; title=&#34;Diagram showing CI/CD pipeline: on pull request, code flows through generate, JSON check, and PR review; on merge to main, Git Sync updates Grafana&#34;&gt;
    &lt;img
      class=&#34;lazyload d-inline-block&#34;
      data-src=&#34;pipeline-flow.svg&#34;
      alt=&#34;Diagram showing CI/CD pipeline: on pull request, code flows through generate, JSON check, and PR review; on merge to main, Git Sync updates Grafana&#34; width=&#34;800&#34; height=&#34;320&#34;/&gt;
    &lt;div class=&#34;learning-hub-image__zoom&#34;&gt;
      &lt;svg width=&#34;24&#34; height=&#34;24&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
        &lt;path d=&#34;M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;/&gt;
        &lt;path d=&#34;M10 7V13M7 10H13&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34;/&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;h2 id=&#34;what-each-stage-does&#34;&gt;What each stage does&lt;/h2&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Stage&lt;/th&gt;
              &lt;th&gt;Trigger&lt;/th&gt;
              &lt;th&gt;Action&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Generate&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Every push and pull request&lt;/td&gt;
              &lt;td&gt;Run Foundation SDK code and produce dashboard JSON&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;JSON check / review&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Pull request&lt;/td&gt;
              &lt;td&gt;Confirm the generated JSON is what the PR intends to merge (for example fail if committed files are out of date)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Git Sync&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;After merge to the synced branch&lt;/td&gt;
              &lt;td&gt;Grafana polls or receives a webhook and updates provisioned dashboards from Git&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;when-each-path-runs&#34;&gt;When each path runs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;On a pull request:&lt;/strong&gt; the pipeline regenerates JSON and gives reviewers a chance to inspect the file changes before merge. Nothing needs to talk to the Grafana API for that review gate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On merge to main&lt;/strong&gt; (or whichever branch Git Sync watches): the merged JSON is in the repository, and Git Sync brings Grafana in line with Git.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Foundation SDK still only generates files. Git Sync remains the sync step you set up in Grafana.&lt;/p&gt;
]]></content><description>&lt;h2 id="cicd-pipeline-flow">CI/CD pipeline flow&lt;/h2>
&lt;p>You can commit dashboard JSON by hand after every SDK change, but that still depends on someone remembering to regenerate and push. A CI/CD pipeline removes that bottleneck.&lt;/p></description></item><item><title>GitHub Actions workflow</title><link>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/02-github-actions-workflow/</link><pubDate>Tue, 11 Aug 2026 10:11:48 +0200</pubDate><guid>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/02-github-actions-workflow/</guid><content><![CDATA[&lt;h2 id=&#34;example-workflow&#34;&gt;Example workflow&lt;/h2&gt;
&lt;p&gt;This example uses GitHub Actions to regenerate dashboard JSON whenever you open a pull request or push to &lt;code&gt;main&lt;/code&gt;. On a pull request it fails if the JSON files in the repo do not match what the generator produces, so reviewers know the committed files are current. After merge, Git Sync (not this workflow) updates Grafana from the repository.&lt;/p&gt;
&lt;p&gt;Adapt the generate command and output path to match your project. The pattern is what matters: generate in CI, review JSON on the PR, let Git Sync sync after merge.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;YAML&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: Generate Grafana dashboards

on:
  push:
    branches: [main]
  pull_request:

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: &amp;#39;1.24&amp;#39;

      - name: Generate dashboard JSON
        run: go run ./cmd/generate

      - name: Check JSON is up to date
        if: github.event_name == &amp;#39;pull_request&amp;#39;
        run: |
          git add -N dashboards || true
          git diff --exit-code -- dashboards&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;step-breakdown&#34;&gt;Step breakdown&lt;/h2&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Step&lt;/th&gt;
              &lt;th&gt;Purpose&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Checkout&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Access your repository code&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Set up Go&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Install the language runtime for your SDK code (use Node setup instead for TypeScript)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Generate JSON&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Run your SDK program and write dashboard files into the synced path (here, &lt;code&gt;dashboards/&lt;/code&gt;)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Check JSON is up to date&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;On pull requests, fail if generated output differs from what the branch commits&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;p&gt;There is no Terraform apply step and no &lt;code&gt;gcx&lt;/code&gt; push in this happy path. After merge, the JSON on the synced branch is what Git Sync reads.&lt;/p&gt;
&lt;h2 id=&#34;alternative-ci-systems&#34;&gt;Alternative CI systems&lt;/h2&gt;
&lt;p&gt;You can translate the same stages to GitLab CI, CircleCI, or Jenkins. Your Foundation SDK code stays the same; only the pipeline syntax changes.&lt;/p&gt;
]]></content><description>&lt;h2 id="example-workflow">Example workflow&lt;/h2>
&lt;p>This example uses GitHub Actions to regenerate dashboard JSON whenever you open a pull request or push to &lt;code>main&lt;/code>. On a pull request it fails if the JSON files in the repo do not match what the generator produces, so reviewers know the committed files are current. After merge, Git Sync (not this workflow) updates Grafana from the repository.&lt;/p></description></item><item><title>Secrets and variables</title><link>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/03-secrets-and-variables/</link><pubDate>Tue, 11 Aug 2026 10:11:48 +0200</pubDate><guid>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/03-secrets-and-variables/</guid><content><![CDATA[&lt;h2 id=&#34;what-this-pipeline-needs&#34;&gt;What this pipeline needs&lt;/h2&gt;
&lt;p&gt;When you connected Git Sync, Grafana got credentials to read your Git repository (for example a personal access token or another auth method). The CI workflow only regenerates JSON and checks that the repo files match. It does not call the Grafana API to deploy.&lt;/p&gt;
&lt;p&gt;For that generate-and-check pattern you often need &lt;strong&gt;no Grafana secret in GitHub Actions&lt;/strong&gt;. GitHub provides &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; automatically for checkout and related steps.&lt;/p&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Value&lt;/th&gt;
              &lt;th&gt;What it is&lt;/th&gt;
              &lt;th&gt;Usually required for generate-and-check?&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;GITHUB_TOKEN&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Built-in token GitHub Actions uses for repository operations&lt;/td&gt;
              &lt;td&gt;Provided automatically&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Grafana service account token&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;API credential for talking to Grafana directly&lt;/td&gt;
              &lt;td&gt;&lt;strong&gt;No&lt;/strong&gt; for this happy path (Git Sync applies changes)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Language / registry secrets&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Tokens for private modules or package registries&lt;/td&gt;
              &lt;td&gt;Only if your generate step needs them&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;when-you-would-add-grafana-credentials&#34;&gt;When you would add Grafana credentials&lt;/h2&gt;
&lt;p&gt;Add a Grafana token to repository secrets only if your pipeline also talks to Grafana (for example a separate &lt;code&gt;gcx&lt;/code&gt; or API step). That is outside this journey&amp;rsquo;s happy path. Keep secrets in &lt;strong&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/strong&gt;, and never commit tokens to the repository.&lt;/p&gt;
&lt;h2 id=&#34;keep-git-sync-auth-in-grafana&#34;&gt;Keep Git Sync auth in Grafana&lt;/h2&gt;
&lt;p&gt;Do not move your Git Sync repository credentials into the Actions workflow unless you have a specific reason. Git Sync authentication stays in Grafana under &lt;strong&gt;Administration &amp;gt; General &amp;gt; Provisioning&lt;/strong&gt;.&lt;/p&gt;
]]></content><description>&lt;h2 id="what-this-pipeline-needs">What this pipeline needs&lt;/h2>
&lt;p>When you connected Git Sync, Grafana got credentials to read your Git repository (for example a personal access token or another auth method). The CI workflow only regenerates JSON and checks that the repo files match. It does not call the Grafana API to deploy.&lt;/p></description></item><item><title>Reliable, repeatable updates</title><link>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/04-create-or-update/</link><pubDate>Tue, 11 Aug 2026 10:11:48 +0200</pubDate><guid>https://grafana.com/docs/learning-hub/dashboards-as-code-git-sync/04-cicd-automation/04-create-or-update/</guid><content><![CDATA[&lt;h2 id=&#34;same-uid-same-dashboard&#34;&gt;Same UID, same dashboard&lt;/h2&gt;
&lt;p&gt;A reliable pipeline produces the same result no matter how many times you regenerate and merge. For Git Sync, that depends on a stable &lt;strong&gt;dashboard UID&lt;/strong&gt; in your Foundation SDK code.&lt;/p&gt;
&lt;p&gt;When Git Sync pulls dashboard JSON, Grafana overwrites an existing dashboard that has the same &lt;code&gt;uid&lt;/code&gt;. If the UID changes every run, you risk creating additional dashboards instead of updating one.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;builder := dashboard.NewDashboardBuilder(&amp;#34;My Dashboard&amp;#34;).
  Uid(&amp;#34;my-dashboard&amp;#34;) // Same UID = update, not a second dashboard&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;what-to-expect-after-merge&#34;&gt;What to expect after merge&lt;/h2&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Situation&lt;/th&gt;
              &lt;th&gt;What happens&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;Same UID, updated JSON in the synced path&lt;/td&gt;
              &lt;td&gt;Git Sync updates the existing dashboard (Grafana overwrites the dashboard with that &lt;code&gt;uid&lt;/code&gt;)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;New UID in the synced path&lt;/td&gt;
              &lt;td&gt;A new provisioned dashboard appears for that file&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;why-the-review-gate-still-matters&#34;&gt;Why the review gate still matters&lt;/h2&gt;
&lt;p&gt;CI that checks generated JSON on the pull request keeps the committed files honest. Reviewers see the JSON diff before merge. After merge, Git Sync applies what is in Git, so the reviewed files are what Grafana receives.&lt;/p&gt;
]]></content><description>&lt;h2 id="same-uid-same-dashboard">Same UID, same dashboard&lt;/h2>
&lt;p>A reliable pipeline produces the same result no matter how many times you regenerate and merge. For Git Sync, that depends on a stable &lt;strong>dashboard UID&lt;/strong> in your Foundation SDK code.&lt;/p></description></item></channel></rss>