Skip to content

Grafana plugins for offline deployment

Declare upstream plugin distributions separately from CMF dependencies. The manifest owns the exact plugin versions; restoring does not update those versions. Packages without grafanaPlugins keep their existing behavior.

{
  "packageId": "Cmf.Custom.Grafana",
  "version": "1.0.0",
  "packageType": "Grafana",
  "grafanaPlugins": [
    {
      "id": "example-custom-datasource",
      "version": "1.2.3",
      "source": "../PluginArchives/example-custom-datasource-1.2.3.zip",
      "platform": "linux-amd64"
    }
  ]
}

This is an illustrative plugin ID/version, not an application dependency or an existing public plugin recommendation. One Grafana cmfpackage.json produces one Grafana ZIP containing both provisioning files and plugins; no companion package, extra manifest or manually configured dependency is needed. A plugins-only package is allowed. Explicitly configured related packages retain their existing behavior.

Declared plugins are installed under /data/grafana/plugins by default, which is the plugin directory used by the CMF Grafana image. Override it with grafanaPluginsTargetPath when deploying to another image:

{
  "packageType": "Grafana",
  "grafanaPluginsTargetPath": "/var/lib/grafana/plugins",
  "grafanaPlugins": []
}
Field Meaning
id Required lowercase, hyphen-separated upstream plugin ID; duplicates are rejected.
version Required exact major.minor.patch, optionally with SemVer prerelease/build metadata. No latest, ranges or wildcards.
source Optional HTTPS ZIP URL or local ZIP path. Relative paths resolve against cmfpackage.json. HTTP, URL credentials and URL fragments are rejected.
sha256 Optional 64-digit hexadecimal digest of the original ZIP. Always checked when supplied.
platform Deployment target: linux-amd64, linux-arm64, linux-arm, darwin-amd64, darwin-arm64, or windows-amd64. Required for backend plugins and architecture-specific catalog artifacts.
grafanaPluginsTargetPath Optional absolute Linux plugin directory. Defaults to /data/grafana/plugins. Root /, trailing slashes, traversal, empty components, Windows paths and glob characters are rejected.

The single-ZIP deployment layout targets the Linux CMF entrypoint, including when packing on Windows. The build machine never determines the plugin platform. Backend distributions must include the executable named by plugin.json for the selected target, including backend components in nested plugin manifests. No downloaded executable is run. Acquisition can validate all listed platforms, but this deployment layout rejects non-Linux platform declarations at pack time.

Build, restore, inspect, pack

cmf build ./Grafana --test
cmf restore ./Grafana
cmf pack ./Grafana --dry-run
cmf pack ./Grafana --outputDir ./Package --force

Build and explicit restore preserve ordinary CMF dependency restoration, then prepare the declared plugins. Build performs this preparation before the package build steps; restore performs it as an explicit preparation command. If source is omitted, the resolver reads the exact version's packages metadata at https://grafana.com/api/plugins/<id>/versions/<version>, selects the declared platform (or any when available), and follows the advertised downloadUrl. Catalog SHA-256 values are also verified when supplied by the catalog.

An HTTPS mirror URL or local archive bypasses the public catalog entirely for that plugin. Failures never fall back to Grafana.com. This guarantee concerns Grafana plugin acquisition; ordinary CMF dependencies still use the configured repositories. Keep local input archives outside directories selected by contentToPack.

Build or restore stores plugin.zip and cmf-grafana-plugin.json beneath the package's .cmf-grafana-plugins/<id>/ folder, separately from Dependencies. Add .cmf-grafana-plugins/ to your project's .gitignore. Packing excludes preparation state, including when a content rule explicitly selects its files.

Preparation records the ID, version, platform, archive SHA-256, and a fingerprint of the requirement including source identity and expected checksum. Source URLs are hashed in metadata so signed query parameters are not stored there. Repeated build or restore reuses verified inputs. Changing any requirement invalidates them; corrupt state is reacquired during the next build or restore. Changing bytes behind an unchanged source does not invalidate a verified cache: change sha256 or remove that plugin's preparation folder to request reacquisition. The original source can be unavailable after preparation.

Pack reads and verifies prepared inputs and never downloads plugins. Missing, corrupt or stale preparation fails with an instruction to run build or restore. For a single Grafana package, dry run lists prepared payload paths and warns about missing prerequisites; it does not download, extract, create archives, or change manifests or preparation state. Explicitly configured related packages retain upstream processing, including its staging-directory behavior. These guarantees concern plugin acquisition. Existing CLI startup version checks and telemetry are not changed by this feature.

Payload, signatures and permissions

manifest.xml
etc/grafana/provisioning/
  <existing selected Grafana files>
data/grafana/plugins/
  <verified-plugin-id>/
    plugin.json
    module.js
    MANIFEST.txt
    <all other distributed files and directories>

The plugin's distribution root is normalized to its verified ID, independently of any npm package.json name. All distributed file bytes, including signature files, are preserved. Nested plugin manifests are retained. The CLI checks identity and integrity, not the cryptographic authenticity of Grafana signatures; Grafana's normal signature verification remains enabled. The new property never becomes a CMF dependency.

With a custom target, the plugin subtree instead uses that exact path without its leading /; the final directory component is preserved, even if it is not plugins. Source contentToPack paths stay relative to the package; the CLI adds the provisioning prefix only in the output. The source manifest is not rewritten with deployment paths.

When declarations are present, source content targeting plugins/ remains reserved; ordinary provisioning content also cannot overlap a declared plugin's destination. Undeclared cached plugins are never included. ZIP paths with traversal, absolute paths, duplicate/case-conflicting names, file/directory collisions, symlinks, special files or unsafe Windows names are rejected before packing. Archives are limited to 256 MiB compressed, 256 MiB per entry, 1 GiB total expanded, 10,000 entries and a maximum 1000:1 expansion ratio for entries over 1 MiB. Downloads have a two-minute timeout and at most five HTTPS redirects. Preparation paths cannot pass through existing filesystem links.

Plugin bytes are copied from the retained ZIP straight into the CMF ZIP, with no filesystem extraction during preparation or packing. Plugin directories are 0755; regular assets are 0644; upstream executable files and the declared target backend are 0755. Privileged and writable-by-everyone bits are removed. The ZIP contains Unix mode attributes and a Unix creator field, including when built on Windows. The Grafana handler applies this metadata only to packages declaring plugins; other package types and Grafana packages without plugins use the existing ZIP writer.

Single-ZIP deployment

The generated manifest.xml sets targetLayer: grafana (by default) and targetLayerDirectory: /. The existing CMF entrypoint honors this package-level destination, unlike per-step DeployFiles.targetDirectory. No EnvManager change, startup script, image rebuild or second package is needed.

The CLI prefixes provisioning deployment patterns with etc/grafana/provisioning/ and generates a separate, narrowly scoped DeployFiles step for each declared plugin. There is no blanket extraction at /: manifest.xml remains ZIP metadata, and all ZIP entry paths are relative, without traversal. Provisioning stays at /etc/grafana/provisioning; this layout does not discover an arbitrary image's provisioning directory at runtime.

Custom DeployFiles, TaggedFile and TransformFile steps retain their order. Only adjacent identical DeployFiles steps are coalesced. Relative step paths may use *, ? and ** patterns where applicable; advanced minimatch expressions, unsafe paths and unsupported step types fail packing with an error. TaggedFile patterns must match packaged provisioning files and expand to explicit paths, so the runtime never recursively scans / for tagging. Tagged/transformed file names must not contain glob or URI fragment/escape characters (# and %).

TransformFile.file must identify a packaged provisioning file. Its relativePath is rebased beneath provisioning. Because the runtime uses file as both its ZIP source and destination name, the ZIP also retains a source-only copy at the original relative path; scoped deployment steps do not extract that copy. Conflicting metadata/destination paths and injected targetLayerDirectory or additional steps sections are rejected.

Installation writes declared plugin files, without deleting the plugin root or unrelated image plugins such as criticalmanufacturing-grpc-datasource. A declared plugin with the same ID as an image plugin overwrites matching files. Native extraction does not remove obsolete files from earlier versions; verify same-ID upgrades and Grafana signature checks on the target image. Removing a declaration is not an uninstall.

grafanaPluginsTargetPath does not change Grafana's configuration. Its effective plugin search path must match the selected destination. For the CMF image default, no additional GF_PATHS_PLUGINS override is needed when it already uses /data/grafana/plugins.

Runtime settings for offline deployments

Bundling plugins removes the need to download those distributions at deployment time; it does not change Grafana's runtime settings. Keep the application's existing authentication, data-source and CMF entrypoint settings. Configure the following on the Grafana container's Deployment, not only on a running pod:

GF_INSTALL_PLUGINS=
GF_PLUGINS_PREINSTALL_DISABLED=true
GF_ANALYTICS_REPORTING_ENABLED=false
GF_ANALYTICS_CHECK_FOR_UPDATES=false
GF_ANALYTICS_CHECK_FOR_PLUGIN_UPDATES=false
GF_PLUGINS_PUBLIC_KEY_RETRIEVAL_DISABLED=true

GF_INSTALL_PLUGINS must be an empty value, not the string false. Remove the old plugin/version list from the deployment manifest as well, so later deployments cannot restore the startup downloader. An explicitly empty container variable also overrides a list inherited from the image.

GF_PLUGINS_PREINSTALL_DISABLED controls Grafana's separate background installer on versions supporting preinstallation, including the observed Grafana 12.0.4 runtime. An empty GF_INSTALL_PLUGINS alone does not stop that installer from fetching suggested apps. Check the actual Starting Grafana version in the logs; the CMF image tag or the default plugin path does not identify the Grafana version. Disabling preinstallation does not disable discovery of plugins already present in the image or extracted from the package.

The analytics settings stop usage reporting and update checks. Disabling public-key retrieval stops key downloads, not plugin signature verification; plugins must still validate against a key available to that Grafana runtime. Do not add unsigned-plugin exceptions for the packaged distributions. Existing image-specific exceptions for CMF plugins are separate from this feature.

Additional GF_PATHS_PLUGINS and GF_PATHS_PROVISIONING overrides are unnecessary when the image already uses the selected plugin destination and /etc/grafana/provisioning. Plugin-admin, news-feed, Gravatar and external-snapshot settings are optional for testing package installation; leaving those features enabled can still cause requests when they are used.

These settings are not a network firewall. Enforce public-egress restrictions separately while allowing required internal services. Failed requests to Grafana.com do not prove that all internet access is blocked. The CLI neither applies these environment variables nor manages network policies.

Verify the target environment

Before claiming offline deployment works on a target environment, obtain:

  • The target image's GF_PATHS_PLUGINS or [paths] plugins configuration and volume mounts.
  • Evidence that the generated package-level destination is honored and both provisioning and plugins reach their intended paths.
  • Evidence that Environment Manager extraction and synchronization preserve ZIP Unix execute permissions.
  • A linked integration run with the required image already available and public internet blocked, covering plugin loading, backend execution, panels and restart.

Also review that image's plugin preinstallation and signature-key fetching behavior. This feature does not package Grafana itself, change startup options, disable signature verification, or add a runtime downloader. User-defined deployment steps remain the user's responsibility. Linux ZIP extraction tests establish archive behavior, not Environment Manager's deployment behavior.

Installation logs supplied on 2026-09-21 provided target-runtime evidence for the single-ZIP layout: the unchanged entrypoint extracted provisioning and all five declared plugins to their intended paths, tagging and dashboard provisioning completed, and Grafana 12.0.4 registered the packaged plugins alongside the image's CMF gRPC and OData plugins. ClickHouse and GraphQL backend queries returned status=ok. This demonstrates coexistence and working backend executables in that run, not full visual or restart validation. The same logs showed four background preinstallation requests timing out; a repeat run after disabling preinstallation, with independently verified public-egress restrictions, is still needed before claiming complete offline validation.

References