Back to Blog

GitOps Without ArgoCD: Inside Ankra's Native Deployment Engine

9 min read
On this page

If you created an Ankra cluster after our May 2026 release, there is something missing from it: ArgoCD. No argocd-server, no repo server, no application controller. Your stacks still deploy, drift still self-heals, SOPS values still decrypt, and Git is still the source of truth. The seven pods that used to do the last mile are simply not there.

This post is the engineering story behind that change. Not a takedown of ArgoCD, which served us and thousands of our clusters well, but an honest account of what it cost, what we built instead, how the migration adopts your live resources without restarting a single pod, and what the native engine does not do yet.

The one-sentence version: GitOps is a contract, not a tool. The contract says desired state lives in Git, something reconciles reality against it, and drift gets corrected. Nothing in that contract says the something has to be a seven-pod control plane inside every cluster you operate.

What ArgoCD was costing us

Ankra’s engine has always owned the hard parts of delivery: the dependency graph, the ordering, the lifecycle, the Git write-back. ArgoCD’s job in our architecture was narrower than in most shops, essentially the last mile: render the chart, apply it, report health.

For that last mile, every single cluster carried:

  • Seven pods. argocd-server, argocd-application-controller, argocd-repo-server, argocd-redis, argocd-applicationset-controller, argocd-notifications-controller, and argocd-dex-server. Roughly 500 to 800 MB of RAM, on every cluster, forever.
  • A JWT lifecycle. The Ankra agent authenticated to ArgoCD’s API with tokens that had to be created, cached, refreshed, and rotated. An entire class of “deploy failed because auth expired” incidents existed only because of this hop.
  • An extra round trip for status. Reading addon health meant asking ArgoCD what it thought, over a proxied API call, instead of the agent just knowing.

On a beefy cloud cluster you can shrug at 800 MB. On the fleets we increasingly see, 50 warehouse edge nodes, a k3s cluster on a Raspberry Pi, an OrbStack cluster on a developer laptop, the overhead tax was often bigger than the workloads. And after June’s Hetzner repricing, 800 MB of platform overhead per cluster is not an abstraction; it is the difference between a node size.

There was a subtler cost too. When something went wrong, the answer lived inside ArgoCD’s abstractions. You debugged an Application CR, sync waves, and a repo server cache, when what you wanted to debug was a Helm release.

What the native engine is

The native engine (ankra_native) deletes the middleman. The Ankra agent, which already runs in every cluster and is already the only writer of cluster state, drives the Helm CLI directly: install, upgrade, reconcile, roll back. No shell interpolation, a single wrapped subprocess with an argument list, one release operation at a time per release, serialised through a Kubernetes Lease.

The consequence that matters most day to day: releases land in standard Helm storage, the helm.sh/release.v1 Secrets every Helm tool understands. Which means this works, on any Ankra-managed cluster, with no Ankra tooling involved:

Terminal window
helm list -A
helm history grafana -n monitoring
helm get manifest grafana -n monitoring
helm rollback grafana 4 -n monitoring

Your cluster is debuggable with the ecosystem’s standard tools at 3 AM, even if Ankra’s control plane were unreachable. We consider that a feature on the same tier as any dashboard we could build. There is no proprietary release format, no CR to reverse-engineer, no cache to invalidate. A Helm release is a Helm release.

The engine is selected per addon, with a cluster-level default. New clusters default to native; existing clusters keep ArgoCD until you choose to migrate. Mixed states are fine and normal, which is exactly what makes the migration boring, in the good way.

The reconcile loop, concretely

“We reconcile drift” is easy to say and easy to fake with a timer and a prayer. Here is what the native engine’s sweep actually does, every 60 seconds per cluster:

  1. Recovers stuck releases. Any release sitting in a pending-install, pending-upgrade, or pending-rollback state for more than ten minutes, the classic Helm wedge that usually means a human with --force is coming, gets an automatic fresh helm upgrade --install --atomic. If that fails, the engine rolls back to the last known deployed revision.
  2. Detects drift against the rendered truth. The live resources are fetched in parallel and diffed against helm get manifest, with Helm-injected metadata stripped from both sides so you are comparing intent against reality, not annotation noise.
  3. Self-heals when told to. If drift is found and the addon’s sync_policy.self_heal is on, the addon is marked for update and the reconciler re-runs the upgrade. Someone’s manual kubectl edit quietly stops being the state of production.
  4. Prunes orphans when told to. With auto_prune enabled, resources whose UID is no longer in the rendered manifest are deleted after apply, so removed chart resources do not accumulate as archaeology.
  5. Proves it is alive. Every sweep publishes a heartbeat with the sweep duration and the number of addons inspected, and the platform exposes a reconcile-loop lag metric per cluster. A reconciler you cannot monitor is a reconciler you get to discover has been dead for a week.

Sync windows survived the migration from ArgoCD too: give an addon a cron expression, a duration, and a timezone, and create, update, and self-heal operations only run inside the window. Outside it, the job returns immediately as cancelled with an explicit “outside sync window” message rather than silently queueing into your Friday evening.

Feature parity, honestly

We held the migration until the answer to “what do I lose?” was almost nothing:

CapabilityArgoCD engineNative engine
Self-heal on driftYesYes, per-addon sync_policy.self_heal
Prune orphaned resourcesYesYes, auto_prune plus post-apply orphan sweep
Ignore-differences rules (JSON pointers, jq paths, field managers)YesYes
Sync windowsYesYes, cron plus duration plus timezone
Custom retry policyYesYes
Strict health checksYesYes
Revision history limitYesYes, --history-max
SOPS-encrypted valuesYes, via helm-secrets pluginYes, direct sops --decrypt
GitHub, Bitbucket Cloud, Bitbucket DCYesYes
Multi-source applicationsYesNot yet, single source only

The SOPS row deserves a sentence, because it connects to the secrets post. Under ArgoCD, decryption ran through the helm-secrets plugin on the repo server, with the AGE key deployed to the cluster as a Kubernetes Secret. Under the native engine, the agent fetches the encrypted values file from your Git provider, pulls the key from the platform for just that render, runs sops --decrypt itself, and hands Helm the plaintext. The private key is never stored on the cluster; it exists in a locked-down temporary file for the seconds the decryption takes and is deleted immediately after. Fewer places a key exists is the whole game in secrets management, and this removed one.

One honest limitation on that path: today the native engine decrypts the values file referenced by the addon’s configuration. If you split encrypted values across several files on one addon, consolidate them into that file, or keep that addon on the ArgoCD engine until multi-file support lands.

The migration: adoption, not reinstallation

The part everyone fears about switching deployment engines is the switch. Here is what per-addon migration actually does:

Terminal window
helm upgrade --install --take-ownership

Helm’s take-ownership flow adopts the live resources into Helm storage. Resource UIDs do not change. No pod is recreated. No traffic blips. The only on-cluster mutation is metadata: the app.kubernetes.io/managed-by=Helm label and the release-name and release-namespace annotations. The ArgoCD Application CR is deleted only after the Helm release verifies as deployed.

Before anything runs, the platform shows a per-addon eligibility report: addons whose live resources include kinds the native health rules do not cover yet get flagged, in-flight ArgoCD syncs block until they settle, and multi-source applications are marked not yet migratable. The migration runs per addon, in parallel up to a small concurrency limit, and is resumable across control-plane restarts. Until you explicitly decommission ArgoCD on the cluster, any individual addon can be rolled back to the ArgoCD engine.

And when the last addon has migrated, decommissioning deliberately stops short of uninstalling ArgoCD. Plenty of teams run their own application delivery through the same ArgoCD instance, outside of Ankra’s addons. We clear our managed credentials and step away. Your ArgoCD, your call.

Why this matters beyond the RAM

The resource savings are real but they are the least interesting part.

Debuggability became standard. Every question about a deployment now has a helm answer. Your existing runbooks, your muscle memory, your incident tooling all apply. The abstraction between you and your releases went from “a CD system’s data model” to “the package manager you already use.”

The failure surface shrank. Every component in the delivery path is a component that can be the incident. Removing the ArgoCD hop removed its API, its tokens, its Redis, and its cache from the list of things that can be why production did not update.

The contract stayed identical. This is the point I care most about. Stacks, drafts, approvals, the dependency graph, Git write-back, the AI’s propose-review-record mandate, none of it changed, because none of it ever lived in ArgoCD. It lives in Ankra’s engine and in your repository. Swapping the last-mile executor under a well-defined contract is exactly the kind of change a platform should be able to make without its users rewriting anything. That it shipped as a settings-page button rather than a migration project is the architecture working as intended.

If you run new clusters, you are already on the native engine and there is nothing to do. If you have existing clusters on ArgoCD, the Migrate to native engine button is in your cluster settings, the eligibility report will tell you if anything needs attention first, and the deployment engines documentation covers the details.

Seven fewer pods per cluster, one fewer credential lifecycle, zero fewer guarantees.


Get started: Create a free account on Ankra.

Join our community: Slack

Follow us on: LinkedIn | GitHub

Contact us: [email protected]

ShareXLinkedInHN

Get the next post in your inbox

Platform engineering guides and product updates. No spam, unsubscribe anytime.

Related Posts