An infrastructure starter kit for OVHcloud, built with Terragrunt and OpenTofu. A learning tool and starting point for deploying a secure, sovereign cloud platform on a European provider.
This is the OVH counterpart of the Scaleway Starter Kit. Same philosophy, different provider - compare the two to understand the trade-offs between European cloud platforms.
Internet
│
┌────┴────┐
│ Octavia │
│ LB │
└────┬────┘
│
┌─────────────┼─────────────┐
│ vRack Private Network │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ MKS │ │
│ │ (Kubernetes) │ │
│ │ │ │
│ │ Envoy Gateway │ │
│ │ cert-manager │ │
│ └───────────────────┘ │
│ │
│ ┌───────────────────┐ │
│ │ PostgreSQL │ │
│ │ (Managed DB) │ │
│ └───────────────────┘ │
└───────────────────────────┘
Container Registry
(Harbor-based)
The Load Balancer is not managed by Terraform. Instead, the OVH Cloud Controller Manager (CCM), pre-installed in MKS, automatically provisions an Octavia LB when it detects the Envoy Gateway Service of type LoadBalancer. PROXY protocol is enabled so Envoy can recover real client IPs. This avoids the hardcoded backend IP problem — node upgrades and autoscaling just work.
| Component | OVH Service | Scaleway Equivalent | Notes |
|---|---|---|---|
| Private Network | vRack + VLAN | VPC + Private Network | OVH's vRack is a free L2 backbone spanning all products. Simpler model — no VPC wrapper needed. |
| Kubernetes | MKS (Managed Kubernetes Service) | Kapsule | Free control plane on both. OVH requires OpenStack network IDs (Neutron under the hood). No CNI choice on OVH. |
| Container Registry | Managed Private Registry (Harbor) | Container Registry | OVH runs full Harbor instances with web UI. Plan-based pricing (SMALL €17/mo). Scaleway is pay-per-storage. |
| Database | Managed PostgreSQL | Managed PostgreSQL | OVH auto-generates the DB password. Requires explicit IP whitelisting (even on private network) and enforces TLS. Uses Aiven-based avnadmin user. |
| Load Balancer | Octavia LB (CCM-managed) | Scaleway LB (CCM-managed) | Not Terraform-managed. Provisioned automatically by the K8s Cloud Controller Manager via Envoy Gateway. |
| Gateway | Envoy Gateway (Gateway API) | Envoy Gateway (Gateway API) | Kubernetes Gateway API with PROXY protocol. Replaces ingress-nginx. |
| TLS | cert-manager (Let's Encrypt) | cert-manager (Let's Encrypt) | HTTP-01 challenge for subdomains. DNS-01 for apex domain available via cert-manager-webhook-ovh. |
| Secret Manager | OKMS Secret Manager (beta) + ESO | Secret Manager + ESO | OKMS exposes a Vault-compatible KV2 API. Terraform creates the OKMS instance; secret values are pushed via scripts/push-secrets.sh (kept out of TF state). ESO uses the Vault provider with a PAT for auth. |
| Observability | Coming soon | Cockpit (Grafana) |
network
├── kube
└── database
okms (independent)
registry (independent)
infrastructure/
├── root.hcl # Shared Terragrunt config (S3 backend, provider)
├── modules/ # Reusable Terraform modules
│ ├── network/ # vRack private network + subnet
│ ├── kube/ # MKS cluster + node pool
│ ├── database/ # Managed PostgreSQL
│ ├── registry/ # Managed Private Registry (Harbor)
│ └── okms/ # OKMS instance (shell only, no secret values)
└── dev/ # Dev environment
├── env.hcl # Environment-specific variables
├── network/terragrunt.hcl
├── kube/terragrunt.hcl
├── database/terragrunt.hcl
├── registry/terragrunt.hcl
└── okms/terragrunt.hcl
k8s/ # Kubernetes manifests (deployed via deploy.sh)
├── namespace.yaml # sovereign-wisdom namespace
├── gateway/
│ ├── envoyproxy.yaml # Envoy data-plane config (Octavia LB annotations)
│ ├── gatewayclass.yaml # Links to Envoy Gateway controller
│ ├── gateway.yaml # HTTP/HTTPS listeners with TLS termination
│ ├── clienttrafficpolicy.yaml # PROXY protocol support for real client IPs
│ ├── httproute-redirect.yaml # HTTP → HTTPS redirect (301)
│ └── cluster-issuer.yaml # cert-manager ClusterIssuer (Let's Encrypt)
├── external-secrets/
│ ├── cluster-secret-store.yaml # ESO ClusterSecretStore (Vault provider → OKMS)
│ └── external-secret.yaml # Syncs DB password from OKMS to K8s Secret
└── app/
├── deployment.yaml # Sovereign Cloud Wisdom app
├── service.yaml # ClusterIP Service
└── httproute.yaml # HTTPRoute (ovh.sovereigncloudwisdom.eu)
scripts/
├── deploy.sh # Deploy app to MKS (Helm installs + K8s manifests)
├── push-secrets.sh # Push secret values to OKMS (bypasses TF state)
└── validate.sh # Validate IaC (format, tflint, trivy security scan)
The root Terragrunt config (root.hcl) is environment-agnostic — all environment-specific values live in env.hcl. To add a new environment (staging, prod), just create a new directory with its own env.hcl.
- OpenTofu >= 1.6.0
- Terragrunt >= 0.93.0
- kubectl
- Helm >= 3.0
- An OVHcloud account with a Public Cloud project
In the OVH console, go to Public Cloud → Object Storage and create an S3-compatible bucket:
- Name:
ovh-starter-kit-tfstate - Region:
GRA - Type: Standard (S3)
Then create an S3 user under Object Storage → S3 users for Terraform state access.
Generate OVH API credentials at https://api.ovh.com/createToken/ with GET, POST, PUT, DELETE rights on /*.
cp .env.example .envEdit .env with your credentials:
# OVH API credentials
export OVH_ENDPOINT="ovh-eu"
export OVH_APPLICATION_KEY=<your-application-key>
export OVH_APPLICATION_SECRET=<your-application-secret>
export OVH_CONSUMER_KEY=<your-consumer-key>
# Public Cloud project ID
export OVH_CLOUD_PROJECT_SERVICE_NAME=<your-project-id>
# S3 credentials for Terraform state backend
export AWS_ACCESS_KEY_ID=<your-s3-access-key>
export AWS_SECRET_ACCESS_KEY=<your-s3-secret-key>
# OKMS Personal Access Token for External Secrets Operator
# Create in OVH Console → IAM → Users → Personal Access Tokens
export OVH_OKMS_TOKEN=<your-okms-pat>
export KUBECONFIG="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/infrastructure/dev/.kubeconfig"Then load it:
source .envcd infrastructure/dev
terragrunt run --all applyTerragrunt will deploy in order: network -> kube + database (parallel). OKMS and the container registry are independent and deploy in parallel with the rest.
Terraform creates the OKMS instance but not the secret data. Push the DB password via the Vault-compatible KV2 API (keeps it out of Terraform state):
./scripts/push-secrets.shUse --dry-run to preview what would be pushed:
./scripts/push-secrets.sh --dry-runAfter the MKS cluster is deployed:
cd infrastructure/dev/kube
terragrunt output -raw kubeconfig > ../.kubeconfig
chmod 600 ../.kubeconfigThen connect to the cluster:
kubectl get nodescd infrastructure/dev/registry
REGISTRY_URL=$(terragrunt output -raw registry_url)
REGISTRY_PASSWORD=$(terragrunt output -raw registry_password)
docker login "$REGISTRY_URL" -u deployer -p "$REGISTRY_PASSWORD"Build and push the sovereign-cloud-wisdom image to the Harbor registry:
# Strip https:// — Docker image references don't include the protocol
REGISTRY_HOST="${REGISTRY_URL#https://}"
docker tag sovereign-cloud-wisdom:latest "$REGISTRY_HOST/library/sovereign-cloud-wisdom:latest"
docker push "$REGISTRY_HOST/library/sovereign-cloud-wisdom:latest"Note: OVH Harbor uses a default
libraryproject. You can create additional projects in the Harbor web UI.
./scripts/deploy.shThe script will:
- Install Envoy Gateway (triggers Octavia LB provisioning via PROXY protocol)
- Apply Gateway API resources (GatewayClass, Gateway, HTTPRoutes, ClientTrafficPolicy)
- Install cert-manager with Gateway API support for automated TLS
- Install External Secrets Operator and sync DB password from OKMS
- Create K8s ConfigMaps from Terraform outputs
- Deploy the app (Deployment, Service, HTTPRoute)
- Print the Load Balancer IP for DNS configuration
Then create a DNS A record:
ovh.sovereigncloudwisdom.eu → <LB IP>
| Aspect | OVH | Scaleway |
|---|---|---|
| Authentication | 3 API keys (app key, app secret, consumer key) | 2 keys (access key, secret key) |
| Networking | vRack (free L2 backbone) + VLANs. No VPC abstraction. | VPC + Private Network. More AWS-like model. |
| Regions | GRA9 = compute, GRA = storage. Same datacenter (Gravelines), different service scopes. |
fr-par / nl-ams / pl-waw. Unified region naming. |
| Kubernetes | MKS. No CNI choice. OpenStack IDs required for private network. | Kapsule. Cilium or Calico CNI. Scaleway IDs directly. |
| Container Registry | Harbor-based. Plan-based pricing (from €17/mo). Web UI included. | Namespace-based. Pay-per-storage (~€0.01/GB). No UI. |
| IaC provider | ovh/ovh. Resources follow ovh_cloud_project_* naming. |
scaleway/scaleway. Resources follow scaleway_* naming. |
| Database | Aiven-based. Enforces TLS. Requires explicit ip_restrictions even on private network. avnadmin is the admin user. |
Built-in. TLS optional. Private network attachment is sufficient. User-defined credentials. |
| Secret Manager | OKMS Secret Manager (beta). Vault-compatible KV2 API. PAT auth. | Native Secret Manager. ESO has a built-in Scaleway provider. |
| State backend | S3 on OVH Object Storage (s3.gra.io.cloud.ovh.net). Needs separate S3 user. |
S3 on Scaleway Object Storage (s3.fr-par.scw.cloud). Uses same API keys. |
cd infrastructure/dev
terragrunt run --all destroyNote: Container registry deletion may take a few minutes. If
destroytimes out, retry.
All resources are deployed exclusively in France (Gravelines datacenter), using OVHcloud — a French cloud provider not subject to US extraterritorial surveillance laws (CLOUD Act, FISA Section 702). OVHcloud is a founding member of the Gaia-X initiative and has achieved SecNumCloud 3.2 qualification (ANSSI) for its Bare Metal Pod and Hosted Private Cloud offerings. Public Cloud SecNumCloud qualification is in progress.