Migrate Grafana from a VM to Kubernetes without breaking users or sign-in
The move from a virtual machine to Kubernetes is mostly about externalizing state first, then repointing URLs and identity, and the effort depends on how many dashboards, plugins, alert rules and external systems depend on the current instance. A Grafana running on a VM often keeps its configuration, users, dashboards and alert state in a local SQLite file, and that file has to leave the VM before anything else. The Grafana installation docs state that SQLite does not scale for production, so any setup with more than a single node should target MySQL 8.0 or later or PostgreSQL 12 or later. Move the data into the external database, verify it on the VM, and only then start pods that point at it. Confirm which database version your platform team supports and whether it will be hosted inside or outside the cluster.
Once the database is external, the rest of the work is a checklist. Map the /var/lib/grafana data directory and any provisioning directories (data sources, dashboards, plugins and alerting resources) to persistent storage or to ConfigMaps and Secrets, and confirm which plugins were installed by hand and need to be reinstalled through configuration. Set root_url and the ingress hostname to the address users will actually type, because OAuth redirect URIs, email links and alert notifications all depend on it. Plan the cutover so the identity provider already lists the new callback URL alongside the old one, since a single missing redirect entry is the most common cause of a failed sign-in. Ask your platform team who owns the ingress and certificates, and test sign-in from a clean browser before the old VM is switched off.
Effort rises with the number of systems that call Grafana. Each API client, CI job, data source connection and alert contact point that refers to the old hostname needs an inventory and a plan, and alert state must be checked on both sides so notifications are neither duplicated nor silently lost. Run the old and new instances side by side for a defined window, and keep the VM and its database backup until users, alerts and automation have been confirmed on the new platform. Ask any partner for a written inventory of these dependencies before quoting, because it determines how much of the work is migration and how much is testing.
What maps cleanly from a VM install to Kubernetes and what does not
This table compares the items your VM-era Grafana depends on with their Kubernetes equivalents and migration notes. Read the rows marked as decisions first, because those are the points where a straight copy of the VM setup will not hold in a pod.
| VM-era item | Kubernetes equivalent | Migration note |
|---|---|---|
| grafana.ini settings | ConfigMap or Helm values, with environment variable overrides | Most keys carry over. Move secrets out of the file and confirm each key against the Grafana version you will run. |
| SQLite file (grafana.db) | External MySQL or PostgreSQL database | Decision. Move the database before the workload, because SQLite on a persistent volume does not suit production or high availability. |
| /var/lib/grafana | PersistentVolumeClaim for plugins and local state | Decision. Keep only what cannot be rebuilt. Database-held state belongs in the external database, not the volume. |
| Provisioning directories (/etc/grafana/provisioning) | ConfigMaps or volumes mounted at the same path | Copy files unchanged. Check that every provisioned data source and dashboard still resolves after the move. |
| Plugins installed with grafana-cli | Plugins set through the GF_INSTALL_PLUGINS variable or a baked image | Decision. Confirm each plugin supports the Grafana version you will run before rebuilding the image. |
| systemd service unit | Deployment, pod security context and probes | Environment variables, user ID and file ownership change. Verify write access to the data path in the pod. |
| Reverse proxy (nginx, Apache) | Ingress or gateway with TLS termination | Keep the public hostname stable where possible. Update forwarded headers and body size limits in the new ingress. |
| Root URL setting (root_url) | Public hostname set in the Grafana configuration | Decision. It must match the external address the browser uses, or links and OAuth redirects break. |
| OAuth client registrations | Same client IDs with updated redirect URIs | Add the new callback URL before cutover and keep the old one until users have signed in successfully. |
| Alert contact points and alert state | Provisioned contact points and an external database for alert state | Decision. Alert state held in SQLite is lost unless the database move preserves it. Verify notifications in a test run. |
Why the database moves first in this migration
A Grafana instance on a single VM usually stores dashboards, users, data source definitions and alert state in a local SQLite file, which is the default. The Grafana installation docs state that SQLite does not scale for production, so this move is the first step that removes a file-bound state store from the migration. Moving the database before Kubernetes lets you test the new backend on the VM you already run, while the old and new configurations are still easy to compare.
- Pick the target engine first: Choose MySQL 8.0 or later, or PostgreSQL 12 or later, based on what your database team already operates and supports.
- Migrate on the VM: Stop Grafana, move the data to the new engine using a supported method, then point the configuration at the new database.
- Keep the SQLite file: Retain a copy of the original file and the configuration as a rollback point until verification is complete.
- Plan for alert state: Confirm how alert state and sessions move, since these are the items most often missed in a dashboard-only check.
- Avoid moving storage and database together: Change one variable at a time so a failure can be traced to one cause.
Before cutover, confirm that users can sign in, dashboards and folders match the source, and alert rules evaluate as expected on the new backend.
If the VM already points at an external MySQL or PostgreSQL server, the database is no longer the risky part of the move, but it still needs a deliberate check. The Kubernetes target will connect to the same server, so connection settings, credentials, network paths and database version all need to work from the cluster before any traffic moves. Treating this as a configuration change to verify, not a data migration, keeps the cutover focused on Grafana itself.
- Confirm the version: Check that the server version is within the range the Grafana docs list for the release you will run.
- Test network reachability from the cluster: Confirm that pods can reach the database host and port through the cluster's egress rules and any firewall in between.
- Move credentials into Kubernetes Secrets: Store the database password in a Secret and reference it from the deployment rather than repeating it in values files.
- Check connection limits: Confirm that the connection count from a scaled-out Grafana deployment fits within the server's limits.
- Back up before cutover: Take a backup of the database that you can restore if the new deployment misbehaves.
After the cutover, verify that the same database is in use by comparing a dashboard change made on the new pod with what the old VM reads, and confirm backups still run on schedule.
A multi-node target is where the external database becomes a requirement rather than a preference. The Grafana high availability docs describe two or more active-active Grafana servers behind a load balancer, sharing a MySQL or PostgreSQL database, with database-backed auth tokens so that a user can reach any node. Every pod in the Kubernetes deployment must read the same database, so the shared state has to exist before the second replica starts.
- Use database-backed auth tokens: Confirm that login sessions are stored in the shared database so a request routed to any pod is accepted.
- Configure alerting for HA: The docs note that alerting needs additional configuration to avoid duplicate notifications, so plan this before scaling out.
- Note Grafana Live limits: Review the documented HA limitations for Grafana Live if your users rely on live streaming updates.
- Set up a shared session path: Make sure the load balancer or ingress does not depend on pod affinity that would break sign-in when pods restart.
- Scale after verification: Start with one replica on the external database, verify, then increase the replica count.
After scale-out, sign in through several pods, confirm alerts fire once, and check that a pod restart does not log users out.
Switching the database backend is only complete when the instance behaves the same as before for users, dashboards and alerts. Verification should compare the old and new state using the same checks, so that a missing item is found while the VM is still available. The goal is to confirm that data moved intact and that the running instance reads from the database you intend.
- Compare object counts: Check that the number of dashboards, folders, data sources, users and teams matches the source.
- Test sign-in paths: Sign in with local and identity-provider accounts, and confirm that sessions persist after a restart.
- Confirm alert evaluation: Verify that alert rules evaluate and that notifications go to the expected contact points without duplicates.
- Check the active database: Confirm from the Grafana logs or configuration which database the running process uses.
- Record the rollback path: Document how to point back to the previous database and what changes after the cutover, so that a rollback decision can be made quickly.
Compare these results against the baseline you recorded on the VM before you move any traffic to Kubernetes.
Documented tools and paths for moving Grafana state
Grafana state lives in several places: the database, provisioning files, plugins, and dashboards or alert rules created through the UI. No single export covers all of it, so the cutover plan maps each piece to the tool that carries it. The cards below cover the documented paths and what each one leaves behind.
Container image and Helm chart
The Grafana installation docs list Docker images and Helm charts for Kubernetes, which replace the VM package install as the runtime. They define the process, image version and mounts, but they do not carry your existing data, plugins or configuration files. Check which grafana.ini settings you override through environment variables or a mounted file, and confirm that the chart version you pick matches the Grafana version you are moving to.
Provisioning files
Provisioning loads data sources, dashboards, plugins and alerting resources from YAML or JSON files at startup. Mounting these files into the Kubernetes pod recreates the configuration as code, so it is the most repeatable way to restore it. It does not move user accounts, folder permissions or alert state, and secret values in data source files must be supplied through Kubernetes Secrets rather than copied into a ConfigMap.
Terraform and Git Sync
Grafana documents Terraform and Git Sync as as-code options for managing resources outside the UI. Terraform state and Git repositories become the source of truth, which helps when several environments need the same dashboards. Neither path copies everything already on the VM automatically, so you will need to export or write the existing resources first. Confirm which resource types your Grafana version supports before you plan the import.
HTTP API for exports
The HTTP API can read dashboards, data sources, folders and alerting resources so you can script an export from the VM instance and import the JSON into the new instance. It is useful for dashboards that were built by hand and never provisioned. It does not export user passwords, the internal database, or alert state history. API paths and response shapes vary by version, so test each export call against your current release and the target release before you rely on it.
Database export and import
Dashboards, users, teams and alert state live in the Grafana database, which is SQLite on many VMs. Moving that file is only safe to plan when the database is externalized first, since SQLite does not scale for production. Confirm which database engine and schema version the new instance uses, and verify the restored counts of dashboards, users and alert rules after the move.
Sequence for a parallel run and cutover from VM to Kubernetes
This sequence moves a running Grafana from a virtual machine to Kubernetes while users keep working on the VM, so each change is verified before users are switched and the VM stays available as a fallback until the new instance has proven itself.
Inventory the virtual machine instance
The platform engineer records the exact Grafana version, the grafana.ini and environment overrides, the database backend, installed plugins, the provisioning directories, every OAuth client and its registered redirect URIs, and the scripts or CI jobs that call the HTTP API. Those API consumers often use legacy API keys or tokens that must be replaced before cutover. The inventory is complete when another engineer can rebuild the instance from it without asking questions, and the list is reviewed with the identity team and the owners of each automation job.
Externalize the database before any move
Before the Kubernetes build starts, the team moves configuration storage from the default SQLite file to MySQL or PostgreSQL, since the installation docs state SQLite does not scale for production. The move runs on the VM first so the database layer is proven in the existing setup. The step is done when the VM runs against the external database, users, dashboards and alert state are confirmed present, and a backup of the new database has been restored successfully in a test location.
Build the Kubernetes deployment with storage and provisioning
The team deploys Grafana on the target cluster with a chart or manifests, pointing it at the external database and the same major and minor version as the VM. Persistent volumes hold the data directory and any plugin directory, and the provisioning files are mounted from a ConfigMap or Git-backed source so data sources and dashboards load at startup. Secrets such as database passwords and OAuth client secrets go into Kubernetes Secrets with encryption at rest configured. The step is done when a fresh pod starts cleanly, loads its provisioned resources and shows no plugin errors.
Pilot the new instance with a copy of the data
The team restores a recent copy of the external database into a separate test database and points the Kubernetes instance at it, using an internal hostname that users cannot reach. Engineers open representative dashboards from each data source, check that panels render, confirm that alert rules and contact points are present, and run the API calls from the inventory. Any broken plugin, missing provisioned item or failed API call is fixed and the pilot is repeated. The step is done when the checklist from the inventory passes against the copy.
Run both instances in parallel
Users stay on the VM while the Kubernetes instance runs on its own hostname against the production database, or a second copy agreed for the trial period. Alerting needs particular care here because two active evaluators can send duplicate notifications, so the team decides which instance sends notifications during the parallel run and disables the other's contact points. The step is done when dashboards, data source queries and alert delivery have been checked on the new instance over the agreed period and the owners of each area have signed off.
Update root_url, ingress and OAuth redirect URIs
In a planned change window, the team sets the root_url in the Kubernetes configuration to the public address, configures the ingress for that hostname and TLS, and adds the new redirect URI to each OAuth client while keeping the old one registered until cutover is confirmed. Identity administrators verify that sign-in works through the new callback, and that cookie and session settings still match the security hardening guidance. The step is done when a test user can complete login on the new hostname and no redirect errors appear in the logs.
Cut over DNS or the load balancer
The team moves the public name to the Kubernetes ingress by changing the DNS record or the load balancer target, using the lowest practical record lifetime set in advance so the switch can be reversed quickly. Engineers watch access logs, login success rates and alert notifications during the first hours, and they keep the VM running in read-only or stopped-service form as a rollback path. The step is done when all users reach the new instance, sign-in works for each identity provider, and no automation job reports failures.
Decommission the virtual machine after verification
After a verified period of stable operation, the team confirms that backups of the new instance run and restore correctly, removes the old redirect URIs from the identity providers, rotates any credentials that were shared during the migration, and then shuts down the VM. The VM's final configuration and database snapshot are archived with the change record. The step is done when the decommissioning checklist is signed by the operations owner and no consumer of the old address remains.
Protecting OAuth callbacks and root_url during cutover
The redirect URI registered with your identity provider must match the public Grafana address after cutover, or sign-in fails at the moment users are moved. On a VM, that address is often a hostname that points straight at the host. On Kubernetes, it is the hostname served by your ingress controller, so the root_url setting in the [server] section of grafana.ini (or the matching GF_SERVER_ROOT_URL environment variable in a Helm values file) must reflect that external address, including any subpath. A mismatch usually shows up as a redirect loop or a callback error after login, not as a clear configuration message. Before cutover, list every OAuth client that points at this Grafana, including generic OAuth, Entra ID, Okta and Google OAuth, and record the exact callback path each one expects. Confirm those paths in each identity provider's console rather than copying them from older documentation.
Plan for a parallel run in which both the old VM and the new Kubernetes instance can receive sign-ins. That usually means registering both the old and new callback URLs with the identity provider for the duration of the migration, then removing the old one once the VM is retired. Confirm that the identity provider allows multiple redirect URIs for the same client and that your security team accepts the temporary extra entry. Also check the edition you run: SAML, team sync and SCIM are available only in Grafana Enterprise and Grafana Cloud, so if your current setup depends on any of them, confirm licensing before the new host goes live. Generic OAuth, Entra ID, Okta and Google OAuth are available across editions, so those integrations carry over without an edition change.
Test sign-in on the new host before any DNS record moves. Use a test hostname or a hosts-file entry that points to the new ingress, sign in with accounts from each identity provider, and confirm that group or role mapping, the login redirect and the logout path behave as they do on the VM. Then move DNS, watch login logs on both sides for failed callbacks, and keep the old redirect URI registered until users have signed in successfully through the new address for a period your change process sets. Document the rollback path: if callbacks fail, the old host and its registered URI must still work.
What drives the cost and duration of a VM to Kubernetes move
This table lists the factors that change the effort of moving a self-hosted Grafana from a virtual machine to Kubernetes, and whether each one is paid once or recurs. Use it to see which details your quote should price explicitly, and which ones you can settle in your own environment before asking for one.
| Driver | Why it changes cost or effort | What to ask for in a quote |
|---|---|---|
| Dashboards and folders | Each folder layout, dashboard JSON and permission set must be exported, checked and imported. Large or nested estates take longer to verify. | Inventory counts, the export and import method, and how visual checks are handled after the move |
| Data source count | Each connection needs its own URL, credentials and network path from the new cluster. Data sources behind firewalls or private networks add routing work. | A per-data-source list covering reachability from the cluster, credential handling and test steps |
| Plugins (signed, unsigned or custom) | Unsigned or custom plugins may need rebuilding, allow-listing or testing against the target Grafana version. Signed plugins usually move with less work. | Plugin inventory with signing status, owner of each custom plugin and the test plan for each |
| Alert rules and contact points | Rules, notification policies and contact points must be exported and re-tested. Notification duplication risk must be checked before cutover. | Rule and contact point counts, export method, and a plan to avoid duplicate notifications during parallel running |
| Identity provider changes | A new ingress host or a new callback URL means the OAuth application at the identity provider must be updated and tested before users switch. | Who edits the identity provider application, the callback URLs to register and a test sign-in sequence |
| Database size and type | A SQLite file must be moved to MySQL or PostgreSQL first. Larger databases take longer to copy, verify and roll back. | Database engine and version, data volume, migration method and the verification steps for users, dashboards and alert state |
| High availability requirements | Two or more active-active servers need shared database sessions, alerting configured to avoid duplicates, and a load balancer in front. Each added node adds configuration and testing. | Node count, load balancer design, and how alerting duplication and Grafana Live limits are handled |
| Kubernetes platform readiness | Cluster version, ingress controller, certificate handling and network policy already in place reduce work. Missing platform pieces are often the largest unplanned item. | A readiness checklist covering the cluster, ingress, certificates, namespaces and access controls, with the owner of each gap |
| Storage class and backup design | Persistent volumes for plugins and provisioning files, plus backups of the external database, must match the storage class offered. Backup and restore tests take time. | Storage class names, volume sizes, backup schedule, restore test scope and who runs the restore drill |
| Change windows and sign-off | Limited cutover windows, a required rollback plan and formal user acceptance add coordination time and may repeat steps. These are usually one-time costs for the move itself. | Agreed cutover window, rollback criteria, acceptance tests and the named approver for each stage |
Where VM to Kubernetes Grafana moves usually go wrong
Most failed Grafana moves from a virtual machine to Kubernetes trace back to state that stayed on the VM, settings that changed with the new hostname, or a second instance quietly doing work it should not. The cards below cover the risks we see most often and a specific check for each one.
Lost SQLite state and alert history
The default SQLite file holds dashboards, users, data source settings and alert state. If the pod is rebuilt from the image without that file, the new instance starts empty or with stale alert state. Before cutover, confirm the target is MySQL 8.0 or later or PostgreSQL 12 or later, that the move was verified table by table, and that a fresh copy of the old database is kept until the new instance has run for a full review cycle.
Plugins missing on the new image
Plugins installed by hand on the VM under the plugins directory do not travel with the container image. Dashboards then show "plugin not found" errors, or a plugin loads but is incompatible with the new Grafana version. Inventory every installed plugin and its version on the VM, decide whether each one is bundled, installed through the plugins setting, or built into a custom image, and test the dashboards that depend on it in staging.
OAuth callbacks rejected after cutover
Identity providers such as Entra ID, Okta, GitHub or Google accept only registered redirect URIs. When the public address changes to the Kubernetes ingress, sign-in fails with a redirect error even though the password path still works. Add the new callback URL to each provider before cutover, keep the old one registered during the overlap period, and test a login from a user account that has no active admin session.
Root URL mismatches breaking links and embeds
Grafana builds links, OAuth redirects, alert notification URLs and embedded panels from its root URL setting. If the value still points at the old VM hostname, or the ingress path prefix is missing, dashboard links and alert messages lead to the wrong address and embeds fail. Set the root URL to the public ingress address, confirm the sub-path behavior if you serve Grafana under a path, and check links in a sent test alert.
Duplicate alert notifications during overlap
Running the VM and the Kubernetes instance at the same time, or running two Kubernetes replicas, can send every alert twice. Grafana's high availability guidance says alerting needs additional configuration to avoid duplicate notifications. Before the second instance receives traffic, decide which one owns alert evaluation, pause or remove the rules on the other, and confirm each contact point receives one notification per event.
Secrets and admin credentials left in the VM config
The VM's grafana.ini often holds the admin password, the database password, OAuth client secrets and the security secret key in plain text. Copying that file into a ConfigMap exposes them to anyone who can read it. Move these values into Kubernetes Secrets with restricted read access, use environment variable overrides for them, rotate the admin password and OAuth secrets after cutover, and remove the old config file from the VM once the new instance is verified.
Questions teams ask before moving Grafana to Kubernetes
How much does a Grafana move from a VM to Kubernetes cost?
The cost depends on the written scope, so no single figure applies. Most of the spend is labor: planning, the database move, building the Kubernetes deployment, testing dashboards and alerts, and the cutover window. Costs that recur include the Kubernetes platform itself, the external PostgreSQL or MySQL database, storage, backups and the time your staff spend on upgrades. Cost goes up when there are many data sources, custom plugins, legacy alert rules, several identity providers or strict change windows. To compare quotes, ask each provider to list the work items, which environments are covered, who runs the cutover, and what is handed over at the end. DeployOpen provides quotes based on a written scope.
How long does a migration take and what sets the timeline?
The timeline is set by scope and by your change process, not by Grafana alone. The main drivers are the number of dashboards, plugins and data sources to check, whether the database must be moved from SQLite first, how many identity and alerting integrations need retesting, and whether you need a parallel run before cutover. Your platform's readiness matters too: a cluster that already has ingress, persistent storage, secrets handling and backups shortens the work. Confirm the approval steps, freeze periods and maintenance windows with your team early, because they often decide the calendar. Ask for the milestones and the dates each one depends on in the written scope.
Can our own team migrate Grafana to Kubernetes without a partner?
Yes, a team with Kubernetes and Grafana experience can run this migration in-house. The questions to answer honestly are whether anyone has operated stateful workloads on the cluster, has built a tested database backup and restore, and can run a cutover that keeps OAuth callbacks working. Teams often use a partner for the parts they have not done before, such as the external database move, the ingress and root_url design, or the identity configuration, while keeping ownership of the environment. If you bring in DeployOpen, the work is planned, implemented and documented inside your own infrastructure under a written scope.
What skills and staff time does the migration need from our team?
Plan for named people in four areas. Kubernetes operators need to review the manifests or Helm values, storage classes and network policies. A database owner needs to approve the PostgreSQL or MySQL instance, its backups and its access. An identity owner must register and test the redirect URLs for the new hostname. A Grafana administrator needs to check dashboards, data sources, plugins, provisioning files and alert rules after the move. Your staff also sign off the change window and the rollback plan. Ask the provider which decisions they expect you to make and how many review sessions to plan for.
Do we need to run the VM and the Kubernetes Grafana side by side?
A parallel run is not always required, but it is the safer choice when dashboards or alerts matter to on-call teams. The two instances can share one externalized database only if you plan that carefully, because alerting can send duplicate notifications when two active instances evaluate the same rules. A common approach is to run the new instance against a copy of the data, compare dashboards and alert states, then switch traffic and disable the old instance's alert evaluation. Decide the length of the overlap, who checks the results and how a rollback would restore the VM service. Your team and provider should agree this in the written scope.
How do we keep single sign-on working during the cutover?
Single sign-on breaks when the new hostname does not match the redirect URI registered with your identity provider. Before cutover, register the new callback URL alongside the old one, set the root_url in the Grafana configuration to the public address users will see, and confirm that the ingress forwards the original host and protocol headers. Check the cookie settings and the session behavior behind the load balancer. Test each sign-in method in a staging copy, including any group or role mapping. Keep the old callback registered until the VM instance is retired. Your identity owner should approve this list before the change window.
What should we ask for in a written scope before we sign?
Ask for a list of the Grafana version and edition, the database engine and its target location, the data sources, plugins and alert rules in scope, the identity methods to test, and the Kubernetes environment where Grafana will run. Ask how provisioning files and persistent volumes will be handled, what the backup and rollback steps are, and what the cutover runbook contains. Confirm which deliverables you receive, such as manifests, documentation and a test record, and what support follows the go-live. A quote should follow directly from this scope, so disagreements about cost are easier to resolve before work starts.
What your team provides and what DeployOpen delivers
The migration goes faster when the inputs are in hand before scope is agreed. On the Grafana side, your team needs read access to the VM, including the grafana.ini file, any environment overrides, the provisioning directory, the plugin directory and the data directory that holds dashboards, uploaded files and plugin state. You also need admin access to the existing database, whether SQLite or an already-external MySQL or PostgreSQL instance, and admin access to the identity provider with the OAuth client settings, redirect URIs and group mappings in use today. On the platform side, confirm which Kubernetes cluster will host Grafana, which namespace and storage class are approved for persistent volumes, and who owns the Helm chart or manifest repository. Ask for a current list of data sources and their credentials, since these move into provisioning or a secrets store and are the most common source of rework.
Several inputs are often owned by people outside the platform team, so name them early. Your team should confirm who controls the DNS record for the public Grafana hostname, who holds the TLS certificate or the issuing process, and whether the ingress controller will terminate TLS. The OAuth callback URL depends on the external hostname and root_url, so a change to either requires the identity provider owner to add the new redirect URI before cutover. Agree on change windows and a rollback window with the owners of the dashboards and alert consumers. DeployOpen plans and implements the migration against a written, fixed scope that lists deliverables, environments, cutover steps and acceptance checks before work starts. Those deliverables typically include the migration runbook, the Kubernetes manifests or Helm values, the provisioning files, the database move plan and the verification report. DeployOpen does not issue audit reports, attestations or certifications. If your organization needs evidence for a compliance assessment, DeployOpen can prepare the configuration records and test results, but an independent auditor or certification body must make the assessment.
How DeployOpen scopes, delivers and supports the migration
DeployOpen plans, builds, documents and hands over the move of an existing Grafana from a virtual machine to Kubernetes inside infrastructure you control. The work starts with a written scope that covers the database, storage, plugins, networking and the OAuth cutover, so your team knows what changes and what is verified before traffic moves.
How we scope
- We inventory the current VM install, its database, provisioning directories, plugins and identity provider settings before any target design begins.
- The written scope names the Kubernetes cluster, ingress, root_url value, persistent volumes and external database that the migration will use.
- Engagements are fixed-scope, with deliverables and timelines agreed in writing before work starts, and a quote follows from that scope.
How we build
- We move the Grafana database off SQLite to MySQL or PostgreSQL first, then verify dashboards, users and alert state against the source.
- We recreate provisioning directories, plugin installs and configuration as code so the Kubernetes deployment starts from a repeatable definition.
- We test ingress, root_url and OAuth callback URLs in a staging copy of the instance before the production cutover window is scheduled.
How we hand over
- You receive the deployment manifests, configuration files and a runbook covering the cutover order and the rollback path to the VM.
- Verification records show which dashboards, data sources, alert rules and sign-in flows were checked, and what remains open.
- We walk your platform and operations engineers through daily operation, including backups of the external database and upgrade steps.
How we support
- Ongoing support, upgrades and continuous testing are available as a retainer after the migration is complete.
- Retainer work can cover Grafana patch and minor upgrades, Kubernetes version changes and checks of the OAuth configuration after changes.
- Before any retainer starts, confirm with us which Grafana and Kubernetes versions your team will run and who owns each on-call step.

