Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

GitOps / Helm / ArgoCD

Moving From Manual kubectl and Helm CLI Deploys to GitOps

Migrating to GitOps means moving what runs in your cluster into git and letting a controller own it. We audit the live state, rebuild it as reviewed manifests or charts, adopt workloads in stages without downtime, then remove manual write access once the git version is trusted.

Key takeaways

  • Do not export the cluster and call it done: live state contains generated fields, secrets and drift that should not be committed.
  • Migrate in waves, starting with low-risk workloads, and prove the process before touching critical services.
  • Adoption without recreation is possible, but needs care with labels, ownership and prune settings.
  • The final step is cultural as much as technical: manual write access must go away, or drift returns.
  • Keep a rollback path for every wave, such as pausing sync and returning to the previous procedure.

What the starting point usually looks like

Few teams begin from a blank page. The typical estate has some services deployed by a pipeline running helm upgrade --install, a few applied by hand with kubectl apply from a laptop folder, an operator or two installed years ago following a README, and a handful of objects nobody can explain. Staging and production have drifted apart. Deployments depend on who is on shift.

Moving to GitOps is how that ends, and it is the capstone of our GitOps, Helm, ArgoCD and Flux service. The work is less about installing a controller and more about getting an honest, reviewable description of what you run. This page lays out how we do it safely, in stages, without a big-bang cutover.

Why not just export everything

The tempting shortcut is kubectl get -o yaml for every resource, commit the result and point a controller at it. It goes wrong in predictable ways:

  • Live objects carry generated fields such as status, resource versions and managed field data, which cause permanent diffs.
  • Secrets would land in git in readable form.
  • Defaults added by admission webhooks and operators get committed as if you wrote them.
  • It preserves every accidental difference between environments, so promotion stays painful.
  • Nobody reviews thousands of lines, so errors move into git unnoticed.

An export is a useful input to the audit. It is not the deliverable. The deliverable is clean, minimal declarations that humans understand.

What good looks like at the end

  • Every workload in the cluster is declared in git or is explicitly listed as out of scope.
  • The controller shows the clusters as in sync, with no unexplained ignore rules.
  • CI has no cluster credentials, and engineers have read access, not routine write.
  • Environments differ only by documented overlays or values, as in multi-environment promotion.
  • A new service is added by a pull request using a known pattern.

How the migration runs

Phase 1: Audit

We compare three sources for each namespace: what is in your repositories, what Helm believes it installed, and what is actually live. Differences are the interesting part. Typical findings include a manually scaled Deployment, an image tag patched in place, a ConfigMap edited during an incident, and a CronJob that exists only in the cluster. Each is classified and a decision recorded.

Phase 2: Design the target

The repository structure, tool choice and installation happen before workloads move, so each migrated service lands in its final home. Secrets are solved first, using the approach in Sealed Secrets or External Secrets, because they block everything else.

Phase 3: Adopt in waves

Wave one is low-risk: an internal tool, a cron job, a staging service. We write the declarations, register the application with sync set to manual, and read the diff. The goal is an empty diff, or a diff you fully explain and accept. Only then do we sync, and later enable automation.

For Helm releases, the safest path is to keep the same chart, release name and values, so the objects are the same and nothing is recreated. A short check before the first sync:

# Compare what the chart would render with what is live
helm template payments ./charts/payments-api -f values-prod.yaml \
  | kubectl diff -f - 

# In ArgoCD, review the diff without changing anything
argocd app diff payments-prod

Pay attention to selectors and immutable fields. A changed Deployment selector, or a StatefulSet volume claim template, cannot be updated in place and will force a recreation, which is the one thing a zero-downtime migration must avoid. We catch those in the diff and plan them separately.

Phase 4: Critical services

Customer-facing and stateful services go last, with a change window agreed, a pre-checked rollback and a second pair of eyes. Prune stays off until we are confident every managed object is correctly labelled and owned. Where progressive delivery fits, it is added after the plain GitOps deploy is stable, not during the migration itself.

Phase 5: Close the doors

The last step decides whether migration sticks. CI jobs lose their kubeconfig, humans lose routine write access under the model in GitOps RBAC, and self-heal is turned on in the environments where it belongs. If any person can still kubectl edit production, drift will return within months.

Decisions and trade-offs

Big-bang or waves. Waves take longer calendar time but each is reversible. Big-bang suits only very small clusters.

Clean rewrite or faithful adoption. A faithful copy gets you running quickly but preserves mess. A rewrite is cleaner but riskier. We usually adopt faithfully, then refactor in git, where changes are reviewable and reversible.

Helm only or Kustomize too. If you already have working charts, keep them. If you have raw manifests, Kustomize is often the lighter first step, with charts added later where reuse justifies them. See writing charts.

When not to migrate yet

If you have one cluster, two services and one engineer, a disciplined pipeline may do for now. If the platform itself is about to change, such as a move to a new cloud, it can be better to land GitOps with that cloud migration rather than twice. If your infrastructure is still hand-built, start with Terraform, covered in our guide on moving from ClickOps to Terraform.

Common failure modes

  • Pruning something important because it lacked the right labels. Prune is enabled last.
  • Fighting controllers, where an HPA, operator or admission webhook changes fields the GitOps tool keeps reverting.
  • Secrets forgotten, discovered only when a pod restarts with empty variables.
  • Half-migrated estates where nobody knows which workloads are managed. We keep a status table per namespace.
  • No communication, so an engineer applies a hotfix by hand and the controller reverts it mid-incident.

Delivery and handover

You receive the audit report, the target repository, migrated workloads, updated pipelines, runbooks and a coached first release by your own engineers. The migration is finished when manual deploy paths are closed, not when the last application turns green.

How to tell it is working

The controller shows a clean state. Rollback is a revert in git. Environment differences are explained. New hires deploy through pull requests on their first week. To scope a migration for your estate, book a consultation.

References

How we deliver it

  1. 01

    Inventory and audit

    We list every workload, how it was deployed, who owns it and what differs between what is in git, in Helm releases and live in the cluster.

  2. 02

    Choose the target model

    The tool and repository layout are decided first, so migrated workloads land in their final structure, not a temporary one.

  3. 03

    Rebuild as declared config

    Manifests are cleaned or turned into charts, secrets are moved to a managed pattern, and generated fields are removed.

  4. 04

    Adopt in waves

    Non-critical workloads go first with manual sync and a diff review, then critical ones, each wave confirmed against live state before sync is automated.

  5. 05

    Turn on enforcement

    Self-heal and prune are enabled per environment once the diff is clean, and CI no longer holds cluster credentials.

  6. 06

    Retire manual access

    Write permissions for humans are reduced, runbooks are updated and the team is coached through its first git-only releases.

Tools and technologies

ArgoCDFluxHelmKustomizekubectlkubectl-neatKubeconformKyvernoSealed SecretsExternal Secrets OperatorGitHub ActionsTerraform

Frequently asked questions

Can we migrate to GitOps without downtime?
Yes, in most cases. The controller can adopt existing resources by matching their names and namespaces, and running with manual sync first lets you review the diff before anything changes. The risk lies in differences between your manifests and the live objects, which is why we compare carefully before enabling automation.
How do we handle existing Helm releases?
You can keep the chart and values, and point ArgoCD or Flux at them so they take over the release. Flux's HelmRelease can adopt an existing release; ArgoCD renders the chart itself and manages the resulting resources. Either way, we match values to what is live and compare the rendered output before syncing.
How long does a GitOps migration take?
It depends on the number of workloads, how consistent they are and how much config lives only in the cluster. A small, tidy estate can move in a few weeks; a long-lived cluster with unknown history takes longer. We give an estimate after the audit, as the audit is what reveals the real scope.
What if the cluster has config nobody remembers creating?
That is common. We export, inspect and classify it: owned by an application, platform component, leftover, or unknown. Unknown items are checked for usage, flagged to owners and either adopted, documented or removed in a controlled step. We never prune what we cannot explain.
Do we have to change our CI pipelines?
Usually yes, in a good way. CI keeps building, testing and publishing images or charts, but stops running kubectl or helm against the cluster. Instead it updates a version in the config repository, and the controller deploys it. Cluster credentials leave your pipeline, which is a security gain.

Talk to an engineer about migrate to gitops

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.