Running Grafana in Docker for production

A quick docker run is fine for a trial, but production Grafana needs a planned database, persistent storage, secrets handling, pinned image tags and a tested upgrade path. This page explains each decision.

On this page

Running Grafana in Docker for production: what changes after the first container

A team that started Grafana with a single docker run command usually needs one change first: a shared instance whose dashboards, users and alert settings survive a container restart, an image upgrade or a host failure. The default setup works for one person testing queries, but it does not meet that need. Grafana stores its configuration in SQLite 3 by default, and the installation docs state that SQLite does not scale for production. Production and high availability require MySQL 8.0 or later or PostgreSQL 12 or later, so the first decision is which external database the container will connect to. Confirm the database version and its network path before you change anything else in the container.

The official Grafana Docker image is a packaged build of the same server that the installation docs describe for Debian, RHEL, SUSE and other platforms, and it is published with a version tag for each release. Those tags are what you pin in production, so an upgrade becomes a deliberate change to the tag rather than an update that happens on restart. A production-grade container setup also means that the container itself is disposable: anything written to the container filesystem disappears when the container is replaced. Plugins, the data directory and any custom configuration must live in a persistent volume, a mounted file or a baked image, and you should verify which of these your team has chosen before the first production deployment.

Teams need this setup as soon as more than one person depends on the instance, or when an alert or dashboard is treated as an operational record. Before you move the container into production, check three things: where Grafana's database lives, where its data directory and plugins are mounted, and how a restart or replacement of the container behaves in your environment. The installation docs also size small deployments at 2 cores and 2 to 4 GB of RAM for under 25 concurrent users, so confirm that the host and any load balancer you plan to add can meet your own user count and query load. Those checks shape the configuration and secrets work that the following sections cover.

Which container options fit which production need

This table compares the container choices that matter when a quick docker run becomes a production service: image variant, storage, database and secret injection. Read each row against the environment you run, then use the right-hand column to decide when a choice is acceptable and when it becomes a risk.

OptionWhat it providesFits whenTrade-off to accept
Official Grafana image (default variant)Maintained upstream image with the Grafana server, configured through GF_ variables and mounted filesStandard Linux container host with a stock plugin set and a registry you can pull fromPlugins must be installed at start or baked into a derived image, and the base OS is whatever upstream ships
Ubuntu-based or other official variantSame Grafana server on a different base OS, useful when your hardening standard names a distributionYour scanning, patching or support policy requires a specific base OSConfirm the current variant names in the official Grafana docs, and check that your vulnerability scanner handles the base
Custom image with baked-in pluginsA derived image that adds approved plugins, certificates and fixed configuration, tagged and promoted by pipelineAir-gapped hosts, strict plugin allow lists or repeatable builds across environmentsYou own rebuilds for every Grafana patch and every plugin update, so the pipeline must exist before go-live
Named volume for /var/lib/grafanaDocker-managed storage that survives container replacement on the same hostSingle host where the database is external and the volume holds plugins and local stateTied to one Docker host, so it does not move with the workload or support replicas
Bind mount to a host pathDirect access to files on the host, easy to back up with host toolingYou need to inspect or back up files with existing host tools or pin storage to a specific diskHost path permissions and ownership must match the container user, and paths differ between hosts
SQLite (default database)Zero-setup configuration database stored inside the Grafana data directoryEvaluation, lab or a single-user instance you can rebuildThe Grafana installation docs say SQLite does not scale for production, and it cannot support high availability
MySQL 8.0 or later or PostgreSQL 12 or laterExternal database holding configuration, users and state, reachable by one or more Grafana containersAny production instance, and the only option for two or more active-active serversYou operate the database, with its own backups, TLS, upgrades and connection limits to plan
GF_ environment variablesOverride individual grafana.ini settings at container start, for example database type and admin settingsSettings that differ per environment and are not secretOnly overrides existing settings, and values appear in container inspection output if not handled carefully
__FILE suffix on GF_ variablesReads a secret value from a mounted file, such as a Docker secret, instead of the environmentPasswords, database credentials and client secrets that must stay out of compose files and image historyRequires a secret store or mounted file process, and the file must be readable by the Grafana process user

How to set up a production Grafana container step by step

This sequence takes a quick local Grafana container to a production-shaped deployment with a pinned image, persistent storage, an external database, secrets passed as files, preloaded plugins, and a proxy-fronted URL. Each step ends with a check you can run before moving on.

  1. Pin the image tag and record it

    Choose the Grafana image variant your team will run and pin it to a specific version tag instead of latest, so every restart and rebuild uses the same binary. Your platform team decides the tag, and it goes into version control with the deployment definition. Check the Grafana releases page and the upgrade guidance to confirm the tag you select is a supported release at the time you deploy. Done when the tag appears in your deployment file and a docker image inspect on the host reports the same version you planned to run.

  2. Mount persistent storage for data and plugins

    Create named volumes or host paths for Grafana's data directory and its plugin directory, and mount them into the container at the paths the Grafana installation documentation describes. Without these mounts, dashboards saved to local state, plugin installs and any SQLite data are lost when the container is replaced. Confirm ownership on the host matches the user the container runs as, or Grafana will fail to write. Done when you stop and recreate the container and the previously created test dashboard and plugin directory are still present.

  3. Point the database at PostgreSQL or MySQL

    Replace the default SQLite file with an external PostgreSQL 12 or later, or MySQL 8.0 or later, database by setting the database type and connection values through GF_DATABASE_ environment variables. The Grafana installation docs state SQLite does not scale for production and is not suitable for high availability, so this step is required for any shared instance. Your database team should create a dedicated Grafana user with rights limited to its schema. Done when Grafana starts without writing a local grafana.db file and the database log shows the Grafana user connecting.

  4. Pass admin and database secrets through __FILE variables

    Store the admin password and database password in files managed by your secret store or the container platform, then reference them with the GF_ variables that end in __FILE, such as the admin password and database password settings. This keeps credentials out of the image, the compose file and shell history. Mount the secret files read-only and restrict their permissions to the Grafana user. Done when docker inspect shows only file paths in the environment, and logging in with the password stored in the secret file succeeds.

  5. Preload plugins or bake them into an image

    Decide whether plugins are installed at startup through the plugin install setting or built into a custom image. Startup installs are simpler but depend on outbound network access and can change between restarts, so pin plugin versions if you use them. A custom image gives repeatable builds and suits restricted networks, and it needs its own rebuild process. Confirm each plugin's license and maintainer with your security team before adding it. Done when Grafana's plugin list shows each approved plugin at its pinned version after a restart.

  6. Expose the container behind a proxy with root_url set

    Put Grafana behind a reverse proxy that terminates TLS, and set the root URL to the public address users will type so links, redirects and OAuth callbacks are generated correctly. A wrong root URL is a common cause of login redirect failures. Keep the Grafana port reachable only from the proxy network. Your network team owns the proxy, certificates and firewall rules. Done when the public HTTPS address loads the login page and the login redirect returns to that same host.

  7. Verify startup, login and data source health

    Check the container logs for a clean startup with no database or plugin errors, log in as a non-admin test user and as the admin, and run the health test on each configured data source from the data source settings page. Record the image tag, volume paths and database host in your runbook so the next upgrade can be compared against this baseline. Done when all data sources report healthy, the test user can open a dashboard, and a restart leaves all configuration and dashboards in place.

How GF_ variables and provisioning override grafana.ini

Grafana reads its settings from grafana.ini, and any setting in that file can be overridden with a GF_ environment variable. The variable name follows the pattern GF_<SECTION>_<KEY> in upper case, so a setting such as root_url in the [server] section becomes GF_SERVER_ROOT_URL. In a Docker deployment this is the layer you will use most, because the container image ships with defaults and the environment is where your platform injects the values for each environment. The important limit is that a GF_ variable can only change a setting that already exists. It cannot add a new key or a new section, so a typo in a variable name is silently ignored. Before you rely on a variable, check its exact name against the configuration reference for your Grafana version and confirm it in a staging container by reading the effective value from the running instance.

Provisioning is a separate layer for objects rather than settings. Files placed in the provisioning directory are loaded at startup and define data sources, dashboards, plugins and alerting resources in YAML or JSON. For a container, this means mounting a directory of files read-only and keeping the file set in version control alongside the image tag, so that a restart recreates the same data sources and dashboards. Decide early which objects are owned by provisioning and which are created by users in the interface, because provisioned objects are normally managed through their files rather than edited in the UI. The provisioning documentation also lists further as-code options, including Terraform and Git Sync, which suit teams that want changes to go through review. Choose one owner for each object type to avoid two systems fighting over the same dashboard. For how to handle secrets in these files and environment variables, see the sibling topic on Grafana configuration with environment variables and secrets.

Container security controls to set before go-live

A production Grafana container inherits the defaults of the image unless you change them. The controls below cover how secrets reach the process, how browser sessions are protected, what the browser is allowed to run, and what an unauthenticated visitor can learn or reach. Work through them in the container definition and verify each one against the running instance before users arrive.

Keep secrets out of the image

Passwords, database credentials and OAuth client secrets should never be written into the Dockerfile, the image layers or a Compose file committed to version control. Grafana reads many settings from environment variables, and for sensitive ones you can point a variable with the __FILE suffix at a mounted secret file, for example GF_SECURITY_ADMIN_PASSWORD__FILE. Check that docker inspect shows only the file path, not the value, and that the secret files are readable only by the Grafana user.

Enable secure and SameSite cookies

Session cookies should only travel over HTTPS and should carry a SameSite attribute that limits cross-site requests. The hardening guidance recommends enabling secure cookies and setting SameSite in the security section of the configuration, which you can also set through GF_SECURITY_ environment variables. Confirm this after you place a TLS-terminating proxy in front of the container, because a misconfigured proxy can leave sessions working over plain HTTP while the flags look correct.

Apply the Content Security Policy

The hardening guidance recommends a Content Security Policy and Trusted Types so the browser refuses scripts and resources that Grafana did not load. Plugins and custom panels that load external scripts or images may break under a strict policy, so test every installed plugin and each external data link in a staging container first. Keep the policy file or setting under version control so changes are reviewed like other configuration.

Hide the version from anonymous users

Grafana's version number helps an attacker match known vulnerabilities to your instance. The hardening guidance recommends hiding it from unauthenticated users, so the login page and public endpoints reveal less about the build. After the change, request the login page and the health endpoint without a session and confirm the version string is absent. Keep the image tag on your own records so you still know what is running.

Require authentication for metrics

Grafana exposes internal metrics for monitoring, and the hardening guidance recommends requiring authentication on that endpoint. If your Prometheus server scrapes it, configure the scraper with a credential or restrict the endpoint to the monitoring network before go-live. Check the metrics path from outside the cluster or network segment and confirm that an unauthenticated request is refused.

Set domain verification against DNS rebinding

Domain verification protects against DNS rebinding, where an attacker's domain resolves to an internal address to reach Grafana from a victim's browser. Set the domain that users actually type in the Grafana configuration so requests for other host names are rejected. Test by sending a request with a different Host header and confirming it is refused, and recheck the setting whenever the public hostname or load balancer changes.

How to upgrade a Grafana container by image tag

Patch releases carry fixes within an existing minor version and are the most frequent change you will make to a production container. Grafana's upgrade guidance says patch releases come at least monthly, so a patch policy is the first thing to write down. Treat a patch as a routine change with a short review, not as a project. Because the minor version stays the same, the database schema and plugin behavior should not shift, but you still confirm this against the release notes for the exact target tag.

  • Cadence: plan a recurring review at least monthly so patches do not drift for long periods.
  • Pinned tags: change one explicit tag, such as grafana/grafana:13.2.4, rather than a floating latest tag.
  • Release notes: read the notes for the target patch before pulling it, especially security fixes.
  • Rollback path: keep the previous tag recorded so you can restart the old container if the new one fails health checks.
  • Test instance: apply the patch to a non-production copy with a similar plugin set first.

Confirm that your monitoring covers Grafana's health endpoint and login path after each restart, because those checks show whether the patch came up cleanly.

Limits to confirm for your Docker setup

A single Grafana container runs well for many teams, but several limits depend on your user count, plugins, identity requirements and data volume. Confirm each one against your own environment before you fix the image, database and sizing.

Plugin support in your image variant

Confirm that the image variant you pick includes every plugin you need, and that each plugin is compatible with the Grafana version you target. Check whether a plugin is bundled, available through the plugin installation variable, or must be baked into a custom image. A plugin that loads on the bench may still fail on a restricted network, so test the exact tag you will run in production.

SQLite against your user count

Grafana stores configuration in SQLite by default, and the installation docs say SQLite does not scale for production. Small deployments are sized under 25 concurrent users at 2 cores and 2 to 4 GB of RAM. If you expect more users, or any high availability, plan for MySQL 8.0 or later or PostgreSQL 12 or later before go-live, because moving a database later adds migration work.

High availability with several containers

Running two or more containers behind a load balancer requires a shared external database and database-backed auth tokens. Review the documented limits before you rely on it: alerting needs extra configuration to avoid duplicate notifications, and Grafana Live has limitations in high availability. Decide which alert routing and live features your users depend on, and test failover with those features turned on.

Enterprise identity features in scope

Basic sign-in options such as LDAP, generic OAuth, Entra ID, Okta, GitHub, GitLab, Google OAuth, auth proxy and JWT are available in every edition. SAML, team sync, active sync and SCIM provisioning are available only in Grafana Enterprise and Grafana Cloud. Confirm with your identity team whether those features are required, because they decide the edition and the license you need before the container is built.

Resource sizing for your data volume

The small-deployment sizing in the docs describes user load, not your data. Query volume, dashboard count, alert rule count, rendering use and the number of data sources all change CPU and memory needs. Set container CPU and memory limits from a test run against representative dashboards and alert rules, and watch memory and database connections after the first weeks of real use.

Persistence and upgrade ownership

Confirm where the data directory, plugin directory and any custom provisioning files live, and that they survive container replacement. Identify who owns the image tag pin, the test environment used before each upgrade and the rollback path if a new release changes plugin or database behavior. Grafana publishes minor releases every other month and patch releases at least monthly, so the upgrade schedule needs a named owner.

Questions teams ask before running Grafana in Docker in production

What drives the cost of running Grafana in Docker in production?

The cost depends on how much of the setup your team builds and how much a partner builds for you. The main drivers are the number of plugins to vet and pin, the database choice, the identity integration, and whether you run a high availability pair behind a proxy. A single container with SQLite is the cheapest to start, but the Grafana installation docs say SQLite does not scale for production, so a MySQL or PostgreSQL database becomes part of the scope. Ask any quote to split one-time work (image design, provisioning files, secrets wiring, cutover) from recurring work (upgrades by image tag, patch review, plugin updates, backups and restore tests). Fixed-scope engagements are agreed in writing before work starts, so the written scope is what the quote should follow.

How long does a production Grafana container setup take?

Duration depends on the number of data sources and users, the identity provider you connect, whether the database and proxy already exist, and how many environments need the same image. A small setup with one instance, one database and a few preinstalled plugins is far simpler than a setup with two active-active nodes, a load balancer, an external database and provisioned dashboards. Your team should confirm the change window, the approval path for production changes, and who signs off on the first upgrade test before anyone commits to a date. A written scope with a timeline is the reliable source for this answer.

What must our team provide before a Grafana container project starts?

Most of the input comes from your side. Expect to provide:

  • A container platform or virtual machines with a container runtime, in an environment you control
  • A PostgreSQL or MySQL database that meets the minimum versions in the Grafana docs, plus backup coverage for it
  • An identity provider and the group or role mappings Grafana should use
  • A secrets store for admin credentials, database passwords and plugin tokens
  • Persistent storage for the data directory and a named owner for it
  • A change window and the contacts who approve production releases

If any of these are missing, the schedule usually moves, so confirm them before the scope is signed.

Can our own team run this without outside help?

Yes, if your team already runs container workloads, a relational database and a reverse proxy. The Grafana docs cover installation, configuration through GF_ variables and the __FILE suffix for secrets, and image upgrades by tag. The harder parts are design decisions: which database and high availability model to use, how plugins are pinned and tested before upgrades, how provisioning files are kept in version control, and how restores are tested. Teams that lack this experience often take longer and make costly changes after go-live. An outside partner can design these pieces, document them and hand over the runbook, or support upgrades on a retainer.

Which Grafana plugins should we preinstall in the container image?

Preinstall only the plugins your dashboards and data sources need, and pin each one to a known version. Every extra plugin adds a compatibility check at each Grafana upgrade and a supply chain review. Confirm each plugin's license and maintenance status with its publisher, and check whether it needs outbound network access at startup. Plugin trust settings and signing rules should be reviewed as part of the image build, not after deployment. If your network is restricted, plan for plugin files to be baked into the image rather than downloaded when the container starts.

When is a Helm chart or the Grafana Operator a better fit than Docker?

Choose a Helm chart or the Grafana Operator when Grafana runs on Kubernetes and your team already manages cluster resources as code. The Helm chart suits teams that want a packaged install with values files for persistence, ingress and plugins. The Grafana Operator suits teams that want Grafana, dashboards, data sources and alerting managed as custom resources in a GitOps workflow. A plain Docker deployment fits single hosts or teams without a cluster. Whichever path you choose, the same questions apply: database choice, secrets handling, upgrade testing and backups. Confirm who supports the chart or operator you select, because community and vendor support differ.

How do we keep Grafana upgrades predictable after go-live?

Upgrades become predictable when every change goes through the same steps: pin the image tag, read the release notes for the target version, test the new image against a copy of your dashboards, plugins and database, then roll it out in a planned window. The Grafana upgrade guidance describes major releases once a year, minor releases every other month and patch releases at least monthly, with each minor release supported for 9 months. Plan the patch cadence in advance, and confirm with your team which versions are in support when you set the upgrade calendar.

How DeployOpen scopes and delivers a production Grafana container

DeployOpen plans, implements, hardens, backs up and documents self-hosted open-source platforms inside the client's own infrastructure, and a production Grafana container is one of those platforms. DeployOpen is a Chainscore Labs company. The work runs in your cloud accounts, data centers or restricted networks, so the image registry, container host, volumes, secrets store and database all stay under your control. The engagement starts with a written scope that names the image variant and tag policy, the persistent volume layout, the GF_ variables and __FILE secrets to be used, any preinstalled plugins, the upgrade method, and the systems Grafana connects to.

Most engagements are fixed-scope. Scope, deliverables and timelines are agreed in writing before work starts, and a quote follows from that written scope rather than from a standard price list, since DeployOpen does not publish one. When you request a quote, ask which items are included: the container build and registry push, the compose or orchestration definition, secrets wiring, backup and restore testing of the database and volumes, documentation for your operators, and handover. Ask also which items are excluded, such as the database service itself, the reverse proxy, identity provider configuration and any plugin development, because those choices change effort more than the container does.

Ongoing support, upgrade work and continuous testing are available as retainers. A retainer suits the recurring part of the work: reviewing each new image tag against the release notes, rehearsing upgrades in a staging environment, rotating secrets and checking that backups still restore. Confirm with DeployOpen during scoping which upgrade and patch windows are covered and how urgent security patches are handled. DeployOpen prepares systems, controls and evidence for compliance assessments but does not issue audit reports or certifications; those come from independent auditors and certification bodies, so confirm with your assessor which records they will need.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.