Running GitOps on a hyperscaler is well-trodden ground. Running it on OVHcloud Managed Kubernetes Service (MKS) is a slightly different exercise: the control plane is free and fully managed, but you get fewer managed side-services than on EKS or GKE. That is actually good news for Argo CD — it pushes you towards a platform stack that lives entirely in Git, is portable, and is not welded to a single provider's IAM and secrets services.
This article covers the practical path: what to know about MKS before installing anything, how to bootstrap Argo CD reproducibly, how to expose and secure it, and the operational traps that show up three months later.
What MKS gives you — and what it does not
OVHcloud MKS runs the control plane for you at no additional cost; you pay for worker nodes, load balancers, block storage and egress. Before designing your GitOps layer, internalise a few structural points:
- No control-plane access. No etcd, no API server flags, no custom admission webhooks at the apiserver config level. Anything you want to enforce must run inside the cluster (Kyverno, Gatekeeper) or before the cluster (Argo CD, CI policy checks).
- Managed CNI. OVHcloud provisions the network plugin for you. Do not plan on swapping it out; design NetworkPolicies against what the cluster actually ships.
- Block storage only, by default. The default StorageClass is Cinder-backed and ReadWriteOnce. There is no trivial ReadWriteMany. If a workload needs shared filesystem semantics, plan for object storage (S3-compatible) or a dedicated NAS instead of hoping for RWX.
- LoadBalancer Services provision a real, billable load balancer. Every
type: LoadBalancerService is a resource with a provisioning delay. Use one ingress controller behind a single LB, not fifteen. - Node pools are the unit of capacity. Autoscaling, upgrade policy and anti-affinity are node-pool level concerns, not cluster-level ones.
The sovereignty angle matters too: for teams under European data-residency constraints, being able to pin every workload to GRA, SBG, DE or WAW while keeping the entire platform definition in a self-hosted Git repository is often the whole point of the exercise.
Bootstrap: Terraform for the cluster, Argo CD for everything else
Draw a hard line. Terraform owns things that must exist before Kubernetes can be talked to: the cluster, node pools, private network, object storage buckets, DNS zones. Argo CD owns everything expressed as Kubernetes manifests. Blur that line and you get two controllers fighting over the same objects.
resource "ovh_cloud_project_kube" "prod" {
service_name = var.project_id
name = "prod-gra"
region = "GRA11"
version = "1.31"
private_network_id = data.ovh_cloud_project_network_private.vrack.id
update_policy = "MINIMAL_DOWNTIME"
}
resource "ovh_cloud_project_kube_nodepool" "workers" {
service_name = var.project_id
kube_id = ovh_cloud_project_kube.prod.id
name = "workers-general"
flavor_name = "b3-16"
autoscale = true
min_nodes = 3
max_nodes = 12
desired_nodes = 3
lifecycle {
ignore_changes = [desired_nodes] # the autoscaler owns this field
}
}
That ignore_changes block is not optional. Without it, every terraform apply will fight the cluster autoscaler and scale your production cluster back down.
Then install Argo CD once, with Helm, from Terraform — and never touch it again by hand:
resource "helm_release" "argocd" {
name = "argocd"
namespace = "argocd"
create_namespace = true
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-cd"
version = var.argocd_chart_version
values = [file("${path.module}/values/argocd.yaml")]
}
The last step of the bootstrap is a single root Application that points back at your Git repo — the app-of-apps pattern. From that moment, Argo CD manages the rest of the platform, including its own Helm release.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
spec:
project: platform
source:
repoURL: https://git.internal.acme.eu/platform/gitops.git
targetRevision: main
path: clusters/prod-gra
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
ServerSideApply=true deserves emphasis. Charts with large CRDs — cert-manager, kube-prometheus-stack, Gateway API — routinely blow past the annotation size limit used by client-side apply. Enabling SSA at the Application level saves you from a class of failures that are annoying to diagnose.
Exposing Argo CD without opening a hole
The pattern that works on MKS: one ingress controller Service of type LoadBalancer, everything else behind it. Argo CD's server runs with --insecure (TLS terminates at the ingress), cert-manager issues certificates, and the DNS-01 challenge is solved through a community OVH webhook driving the OVHcloud API. That last part matters for private clusters, where HTTP-01 is not reachable from the internet.
# values/argocd.yaml (excerpt)
configs:
params:
server.insecure: true
server:
ingress:
enabled: true
ingressClassName: nginx
hostname: argocd.platform.acme.eu
tls: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-ovh-dns01
redis-ha:
enabled: true
controller:
replicas: 2
Do not stop at the ingress. Wire Argo CD to your identity provider through OIDC (Keycloak, Authentik, or your corporate IdP), disable the local admin account once SSO works, and map groups to Argo CD RBAC. A shared admin password stored in a password manager is not an access control model.
policy.csv: |
g, platform-team, role:admin
g, dev-payments, role:payments-deployer
p, role:payments-deployer, applications, sync, payments/*, allow
p, role:payments-deployer, applications, get, payments/*, allow
Then enforce the boundary with AppProjects: restrict source repositories, destination namespaces and allowed cluster-scoped resource kinds per project. Without AppProjects, any team able to create an Application can deploy a ClusterRoleBinding.
Secrets: the part people postpone
MKS does not hand you a deeply integrated cloud KMS/IAM combo the way EKS does with IRSA. Two options work well in practice.
SOPS with age keys is the simplest: encrypted values live in Git, the decryption key lives in the cluster, and a kustomize plugin or the Argo CD SOPS integration decrypts at render time. Low moving parts, works offline, easy to audit. The weak point is key rotation and revocation.
External Secrets Operator is the better fit at scale: secrets stay in a vault (HashiCorp Vault self-hosted on OVHcloud, or an equivalent), and Git only contains references. Nothing sensitive ever reaches the repository, and rotation is decoupled from deployments.
Whichever you pick, the rule is the same: Git contains references or ciphertext, never plaintext. And Argo CD's own repository credentials should be provisioned by Terraform or ESO, not typed into the UI.
Multi-cluster and multi-region with ApplicationSet
OVHcloud makes it cheap to run several clusters — the control planes cost nothing. Many teams end up with prod in GRA, DR in SBG or DE, plus an ephemeral staging cluster. Managing that by copy-pasting Applications does not scale; ApplicationSet does.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: platform-addons
namespace: argocd
spec:
generators:
- matrix:
generators:
- clusters:
selector:
matchLabels: { tier: production }
- git:
repoURL: https://git.internal.acme.eu/platform/gitops.git
revision: main
directories:
- path: addons/*
template:
metadata:
name: '{{path.basename}}-{{name}}'
spec:
project: platform
source:
repoURL: https://git.internal.acme.eu/platform/gitops.git
targetRevision: main
path: '{{path}}'
helm:
valueFiles:
- values.yaml
- 'values-{{metadata.labels.region}}.yaml'
destination:
server: '{{server}}'
namespace: '{{path.basename}}'
syncPolicy:
automated: { prune: true, selfHeal: true }
Region-specific values files keep regional differences (storage class names, LB annotations, DNS zones) explicit instead of hidden in templating logic.
Argo CD or Flux on MKS?
Both work fine. The decision is about team ergonomics, not provider compatibility.
| Criterion | Argo CD | Flux |
|---|---|---|
| UI / diff visualisation | First-class, a real driver of adoption | CLI-first, third-party dashboards |
| Multi-tenant RBAC | AppProjects + SSO, mature | Namespace isolation, more manual |
| Resource footprint | Heavier (controller, repo-server, Redis) | Lighter, several small controllers |
| Fan-out to many clusters | Hub-and-spoke, hub sizing becomes a topic | Naturally per-cluster |
| Progressive delivery | Argo Rollouts | Flagger |
On MKS specifically, Argo CD's UI is often decisive: with no cloud console showing you deployment state, the Argo CD dashboard becomes the platform's control surface for developers.
Operational traps worth anticipating
Cluster upgrades and drains. MKS update policies determine how aggressively nodes are recycled. Without PodDisruptionBudgets, an upgrade will happily evict both Argo CD controller replicas at once. Set PDBs on the platform stack before you need them.
Argo CD managing itself. Elegant, and a foot-gun. A bad values change can leave the controller unable to reconcile the fix. Keep the Terraform-driven Helm release as an escape hatch, and always keep a break-glass kubeconfig.
Pruning and cluster-scoped resources. prune: true combined with a mis-scoped Application has deleted more than one CRD set. Use Prune=false annotations on CRDs and finalizer-bearing resources, and sync waves to order namespaces, CRDs, then workloads.
Backups. The control plane is managed, but your objects are not backed up for you. Run Velero with OVHcloud S3-compatible object storage as the target, and snapshot Cinder volumes. GitOps restores manifests, not stateful data.
Cost drift. Autoscaling plus GitOps makes it trivially easy to add workloads. Track node-pool utilisation and LoadBalancer count; the two most common bill surprises on MKS are over-provisioned node pools and one load balancer per team.
Where this leaves you
The combination of MKS and Argo CD produces a platform whose full definition lives in a repository you control, running in a European region you chose, with no dependency on a proprietary deployment service. Rebuilding the cluster in another region becomes a Terraform apply plus a root Application — which is exactly the property you want when the discussion turns to reversibility or regulatory constraints.
Get the boundaries right — Terraform for infrastructure, Argo CD for manifests, a vault for secrets, AppProjects for tenancy — and the rest is incremental.
