Skip to main content

France's Railway Runs on Cluster API: What SNCF's Monthly Zero-Drift Fleet Teaches a Self-Hosted PaaS

11 min readDora NodaDora Noda
Share
On this page

France's national railway operator provisions a production Kubernetes cluster in 30 minutes, updates every cluster in its fleet monthly, and guarantees zero configuration drift. It does this on infrastructure it owns outright, with no managed control plane. If you run a self-hosted platform and still treat Cluster API as promising-but-unproven at fleet scale, SNCF's production numbers say otherwise.

CNCF published the case study — "Reaching for Strategic Autonomy by Building On-Premises Kubernetes with Cluster API and ArgoCD" — on March 25, 2026, and SNCF went on to win CNCF's 2026 Top End User Award at KubeCon Europe. The interesting question for a small self-hosted team is not whether SNCF succeeded. It is which parts of their setup transfer to a fleet of five machines on Hetzner, and which parts belong to a regulated national operator with a dedicated platform team. This post gives you both, starting with the numbers.

The numbers that end the "CAPI at scale is unproven" debate

SNCF runs critical transportation systems serving millions of passengers daily. After years of operating Kubernetes successfully in public clouds (Azure and AWS), the team tried to reproduce the managed-Kubernetes experience in its own datacenters. Their words for the first attempt: functional but "naive" — manual processes, custom tooling, snowflake clusters. Then they rebuilt from scratch on Cluster API, and the before/after table is the core of the case study:

MetricLegacy on-premisesNew cloud-native platform
Cluster provisioning1 month30 minutes
Fleet growth14 clusters in 4 years10 clusters in 6 months
Day 2 operationsManual and challengingOn-demand and automated
Configuration driftHighZero-drift guarantee

Every Kubernetes cluster in the fleet is now updated monthly. CNCF's writeup is explicit that this "zero-drift" posture — production always in sync with declared desired state — is what provides the reliability national rail operations require.

One precision matters before you quote these numbers elsewhere: the frequently cited "200+ clusters" figure describes SNCF's public-cloud footprint (AWS and Azure, where the team migrated 70% of 2,000 applications), not the on-premises CAPI fleet. The on-prem fleet is the "10 clusters in 6 months" line — smaller, but growing roughly an order of magnitude faster than the 14-clusters-in-4-years era, with monthly updates and a drift guarantee the old platform never had. That distinction actually strengthens the lesson for small teams: you do not need 200 clusters for this discipline to pay off. SNCF's on-prem fleet is proof at a scale much closer to yours than the headline number suggests.

What SNCF actually built

The stack is modular and deliberately boring in the best sense — CNCF-aligned projects with transparent governance, which the team frames as a sovereignty decision as much as a technical one:

  • IaaS layer: OpenStack (Canonical) for compute, networking, and storage primitives.
  • Immutable OS: Talos Linux, API-driven with no SSH and no manual configuration — removing the human-error factor at the node level.
  • Declarative lifecycle: Cluster API, which the case study calls "the heart of the architecture." Clusters are Kubernetes resources managed by a reconciliation loop.
  • Networking and policy: Cilium for eBPF-based networking and observability, Kyverno for policy-as-code compliance across the fleet.
  • GitOps: ArgoCD, already managing hundreds of public-cloud clusters, extended to the new datacenter clusters as one unified management layer.
  • Supply chain: ORAS, managing Cluster API's own providers as OCI artifacts so every high-level primitive has a GitOps-reconciled lifecycle.

The single biggest unlock was node autoscaling on-premises. Using the CAPI OpenStack provider, SNCF got managed-cloud-grade autoscaling in its own datacenters with a unified workflow across hybrid environments. As Senior Staff Engineer Yann Rotilio put it: "Cluster API was a game-changer. It gave us node autoscaling in our datacenters — something we thought was only possible with AKS or EKS."

Note the operating principle underneath the tool list. Rotilio again, on why they rebuilt instead of patching: "applying incremental fixes on unsteady foundations would prove time-consuming without any guarantee of fixing the underlying issues. We needed to start again entirely." Monthly zero-drift updates are not a feature you bolt onto snowflake clusters. They are what falls out of a declarative foundation.


Why a monthly zero-drift cadence is mechanically possible

"Updated monthly, zero drift" sounds like a staffing achievement. It is mostly a machinery achievement — four mechanisms doing the work:

1. The reconciliation loop never sleeps. CAPI controllers continuously compare actual state against declared Cluster and MachineDeployment objects and act to close the gap. Drift is not detected at audit time; it is corrected as a background property of the system. SNCF's "zero-drift guarantee" is this loop treated as policy rather than aspiration.

2. Managed topologies roll the fleet in lockstep. With ClusterClass and topology, one template change — a new Kubernetes version, a new machine image — propagates to every Cluster referencing it as a rolling update, instead of requiring N hand-edited cluster definitions. This is what turns "update the fleet" from a quarterly project into a monthly routine: the unit of work is one template edit, not one cluster at a time.

3. GitOps self-heal reverts the human. ArgoCD continuously compares rendered Git manifests against live state, and with automated sync plus self-heal, a manual kubectl edit gets reverted rather than lingering as invisible drift. Git history becomes deployment history, and rollbacks become reverts. SNCF extended the ArgoCD setup it already ran for public cloud, so the datacenter clusters inherited drift detection and correction on day one.

4. Each cycle keeps getting cheaper. Cluster API v1.12 (February 2026) added in-place updates and chained upgrades: low-disruption changes apply without replacing machines, and a cluster several minors behind can jump straight to the target while CAPI walks the intermediate steps itself, skipping worker-machine upgrades that version-skew policy allows skipping. Every release in this direction lowers the cost of the next monthly cycle — the opposite of the legacy pattern where each skipped upgrade makes the next one scarier.

Together, these four explain the cadence. Monthly is not heroic when the machinery does the reconciling, the template does the fan-out, GitOps does the policing, and the upgrade path does its own step-walking.


Monthly unattended on owned Hetzner without OpenStack

SNCF's stack has one layer a five-machine fleet will never build: OpenStack. That is fine, because OpenStack is not load-bearing for the discipline — it is just SNCF's infrastructure provider. The load-bearing pieces are the CAPI contracts above it, and the Hetzner substitute is a straight swap: run the Cluster API Provider Hetzner (CAPH) where SNCF runs the CAPI OpenStack provider. Same Cluster and MachineDeployment objects, same reconciliation loop, same ClusterClass lockstep — different machines underneath.

What changes without OpenStack is the autoscaling story's texture, not its existence. SNCF got node autoscaling inside its datacenters via CAPI plus cluster-autoscaler against OpenStack capacity. On Hetzner, CAPH pairs with cluster-autoscaler against your Hetzner project quota the same way: declare minimum and maximum replicas, let demand drive machine count. The capacity-headroom policy is simpler (your quota is the ceiling, not a datacenter procurement cycle), but the mechanism is identical.

The harder question is what "unattended" means when there is no SNCF-sized platform team watching. Three runbook pieces make monthly cadence safe without headcount:

  • Phased rollout with a pause button. Do not let a ClusterClass change hit every cluster simultaneously. Sequence the fleet (dev, then staging, then production tenants), gate each phase on health checks, and keep CAPI's pause annotations wired to a single obvious control so a human can freeze the rollout in seconds.
  • Rollback by revert, verified by drift alerting. Because the template lives in Git, rollback is a revert plus ArgoCD sync. The alert that matters is not "deploy failed" but "a cluster has been OutOfSync or Degraded for longer than X" — ArgoCD exposes sync and health status per Application, so alert on the aggregate (argocd app list filtered to non-Synced) rather than eyeballing a dashboard.
  • Monthly-default versus quarterly-manual, honestly compared. A quarterly manual upgrade project concentrates risk into rare, high-stakes events performed by whoever remembers the last one. A monthly default distributes the same total change into smaller, routine, increasingly automated cycles — each one cheaper than the last thanks to the v1.12-era machinery. The failure mode to respect is the unattended cycle that fails silently, which is exactly what the drift alert above exists to catch.

None of this requires OpenStack, Kyverno breadth, or a railway's compliance apparatus. It requires the provider swap, the phased rollout, and one good alert.

Portable vs rail-specific: what a small fleet can copy

Here is the verdict table — each SNCF practice mapped to what a self-hosted team on owned hardware should do with it:

SNCF practiceVerdict for a small fleetNotes
GitOps-driven Cluster/MachineDeployment manifestsAdopt as-isThe single highest-leverage copy. ArgoCD or Flux against your management cluster; Git becomes fleet history.
Talos Linux / immutable, SSH-less nodesAdopt as-isRemoves the largest drift source (humans on nodes) for near-zero ongoing cost.
Monthly reconcile-as-policy cadenceAdopt as-isStart with one canary cluster, then extend the cadence fleet-wide.
Drift alerting on sync/health statusAdopt as-isOne alert on non-Synced Applications; the cheapest insurance in this post.
CAPI providers as OCI artifacts (ORAS)Adopt once bittenWorth it when provider-version skew across clusters has hurt you once; overkill on day one.
OpenStack IaaS layerSubstituteCAPH on Hetzner fills the same contract. Do not build a private cloud to copy a private cloud.
Kyverno policy breadth for complianceAdaptKeep a small policy set (no :latest, required labels, resource limits); skip the regulatory catalog.
Dedicated platform-team headcountAutomate insteadSNCF's monthly discipline was built with people; yours must be built with the phased rollout plus the drift alert above.
KCP / Crossplane next-gen abstractionWatchSNCF's stated future direction; not a prerequisite for any of the above.

The pattern: everything about declaring desired state and reconciling toward it ports directly. Everything about which substrate or which regulator stays behind. "Zero drift" was never about the railway — it was about the loop.

Your Monday-morning checklist

Five concrete steps, ordered by leverage, to move a small fleet toward monthly unattended reconciliation:

  1. Put one cluster's CAPI manifests in Git behind ArgoCD or Flux today. Not the fleet — one cluster. Prove the sync/self-heal loop on something you can break.
  2. Add the drift alert before the next change window. Alert on any fleet Application non-Synced or Degraded beyond your threshold. From here on, drift pages; it does not accumulate silently.
  3. Convert one cluster to a managed topology. Define the ClusterClass, migrate the cluster to it, and execute your next Kubernetes upgrade as a template edit. Measure the hours; that number sells step 4.
  4. Schedule the monthly cycle and run it twice manually. Same week each month, phased across clusters, with the pause control tested. Two manual runs calibrate the runbook; the third can start unattended with the alert as backstop.
  5. Kill SSH to nodes on the next image rotation. Move new machines to Talos or an equivalently immutable image. Every manual node change you prevent is drift your loop never has to correct.

The managed experience is an operational standard, not a location

SNCF's closing line in the case study deserves the last technical word: "the 'managed cloud experience' isn't a location — it's an operational standard that can be achieved anywhere." Thirty-minute provisioning, monthly updates, zero drift, on-premises autoscaling: each was previously filed under "only AKS or EKS can do that," and each turned out to be a property of declarative machinery plus cadence discipline. A national railway proved it with a platform team; the machinery itself is open source and indifferent to your headcount.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Sources

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex