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.
| Option | What it provides | Fits when | Trade-off to accept |
|---|---|---|---|
| Official Grafana image (default variant) | Maintained upstream image with the Grafana server, configured through GF_ variables and mounted files | Standard Linux container host with a stock plugin set and a registry you can pull from | Plugins must be installed at start or baked into a derived image, and the base OS is whatever upstream ships |
| Ubuntu-based or other official variant | Same Grafana server on a different base OS, useful when your hardening standard names a distribution | Your scanning, patching or support policy requires a specific base OS | Confirm the current variant names in the official Grafana docs, and check that your vulnerability scanner handles the base |
| Custom image with baked-in plugins | A derived image that adds approved plugins, certificates and fixed configuration, tagged and promoted by pipeline | Air-gapped hosts, strict plugin allow lists or repeatable builds across environments | You own rebuilds for every Grafana patch and every plugin update, so the pipeline must exist before go-live |
| Named volume for /var/lib/grafana | Docker-managed storage that survives container replacement on the same host | Single host where the database is external and the volume holds plugins and local state | Tied to one Docker host, so it does not move with the workload or support replicas |
| Bind mount to a host path | Direct access to files on the host, easy to back up with host tooling | You need to inspect or back up files with existing host tools or pin storage to a specific disk | Host 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 directory | Evaluation, lab or a single-user instance you can rebuild | The 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 later | External database holding configuration, users and state, reachable by one or more Grafana containers | Any production instance, and the only option for two or more active-active servers | You operate the database, with its own backups, TLS, upgrades and connection limits to plan |
| GF_ environment variables | Override individual grafana.ini settings at container start, for example database type and admin settings | Settings that differ per environment and are not secret | Only overrides existing settings, and values appear in container inspection output if not handled carefully |
| __FILE suffix on GF_ variables | Reads a secret value from a mounted file, such as a Docker secret, instead of the environment | Passwords, database credentials and client secrets that must stay out of compose files and image history | Requires 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.
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.
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.
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.
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 inspectshows only file paths in the environment, and logging in with the password stored in the secret file succeeds.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.
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.
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 floatinglatesttag. - 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.
Minor releases add features and can change defaults, deprecate settings or alter data source and plugin behavior. Grafana's upgrade guidance says minor releases arrive about every other month. Each minor release is supported for 9 months, so you need to move forward on a schedule that stays inside that window. A minor upgrade is a planned change with a staging run, a read of every intermediate release note and a check of each plugin's compatibility with the target version.
- Support window: each minor release is supported for 9 months, so track the end-of-support date when you choose a tag.
- Upgrade notes: read the breaking changes and deprecations for every minor between your current version and the target.
- Plugin compatibility: confirm that each installed plugin supports the target Grafana version before you change the tag.
- Staging run: start the new tag against a restored copy of the production database and volume.
- Dashboard review: open key dashboards and alert rules in staging to confirm queries and panels still render.
Illustrative example: if your team runs a release that enters end of support in a given month, it should schedule the next minor move well before that date, since the upgrade needs testing time.
Major releases are the largest changes. Grafana's upgrade guidance says they come once a year, in April or May. A major release can remove features, change configuration, and require action on data sources, plugins, storage or automation before or after the change. Treat it as a planned project with a written migration plan, a rehearsal in staging and a named owner for each downstream dependency. The last minor release of a major version is supported for 15 months, which gives teams more time, but it does not remove the need to plan the move.
- Support window: the last minor of a major version gets 15 months of support, which you should record as the deadline for the next major move.
- Technical notes: read the notes for each major release and review changes to data sources, plugins, storage and provisioning files.
- Automation review: check scripts, CI jobs and provisioning files that reference settings or APIs that may change.
- Plugin owners: confirm which team owns each plugin and when it can test the new version.
- Sign-off: record who approves the cutover and who can roll back.
Confirm with your own change board whether a major move needs a formal change window, since that depends on your internal governance.
Every tag change should start with a backup of the database and the persistent volume. Grafana stores configuration and state in SQLite by default, MySQL or PostgreSQL. The installation docs say SQLite does not scale for production, so most production containers use an external database, which should be backed up on its own schedule. The container image is disposable, but the volume and database are not, so the backup must capture both at a consistent point. Without that pair, a failed upgrade can leave you with a new schema and an old data directory, or the reverse.
- Database backup: take a logical or snapshot backup of the Grafana database immediately before the change, and record its location.
- Volume backup: copy the persistent volume mounted at
/var/lib/grafanawhile the container is stopped. - Configuration copy: save the current environment variables, provisioning files and secret references.
- Restore test: confirm that the backup restores into a separate instance before you rely on it.
- Rollback steps: write down the exact commands to restore the previous tag, volume and database together.
Before you begin, confirm with your database team how it takes consistent backups, since the answer depends on whether the database runs in your own infrastructure or a managed service.
A tag change is not finished when the container starts. Verification confirms that the new version serves users, reads its data sources, runs alert evaluation and loads plugins without errors. Use a checklist that covers the login path, data source health, dashboards, alert rules and notification routing, and the logs for startup warnings. Keep the previous tag available until the checks pass and the owners of key dashboards confirm their panels work. Where Grafana runs in high availability, upgrade one node at a time and check that alerts do not fire twice during the rolling change.
- Health checks: confirm the health endpoint responds and the container stays up after the first few minutes.
- Data sources: run the built-in data source test for each connection and review failing panels.
- Alerting: confirm rules evaluate and notifications reach their contact points.
- Logs: scan container logs for deprecation messages or plugin load failures.
- Rolling changes: when more than one node serves users, change one node, verify it, then change the next.
Record the verification results and the final tag in your change record, so auditors and the next operator can see what ran and when.
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.

