Setup guide
Everything you must provision before this skill can run โ the exact credentials, permissions, and where to click. Links go to the official consoles and docs, which stay current.
Provision an OVHcloud account on the correct legal entity (ovh-eu / ovh-us / ovh-ca), create a Public Cloud project, then issue three separate credential sets the skill uses in parallel: an OVH API OAuth2 service account for the /cloud API and the ovh/ovh Terraform provider, an OpenStack project user plus openrc.sh for the OpenStack and Octavia CLIs, and an S3 access key pair for Object Storage.
You need to already have
- An OVHcloud account (NIC handle) on the legal entity that matches the target endpoint. ovh-eu, ovh-us and ovh-ca are separate companies with separate APIs and non-portable credentials; EU data residency work must use ovh-eu.
- Account owner or an IAM identity holding account:apiovh:* rights, so you can create OAuth2 service accounts, legacy API tokens and IAM policies.
- A Public Cloud project with a validated payment method. New projects require identity/credit-card verification before any instance, MKS cluster or bucket can be created.
- The Administrator role on that Public Cloud project, required to create OpenStack project users and generate S3 credentials.
- Local tooling: Terraform >= 1.5 or OpenTofu, ovh/ovh provider ~> 2.0, python-openstackclient, python-octaviaclient, awscli v2, kubectl and jq.
- A quota-increase ticket opened in advance if the design exceeds the per-region defaults of roughly 20 instances / 20 vCores / 40 GB RAM (GPU quota is separate and always starts at 0).
- An existing vRack service, ordered separately, only if the project must share layer 2 with bare metal or Hosted Private Cloud.
How the pieces connect
Credentials this skill needs
Set these as environment variables. Never paste secrets into a chat or commit them.
| Variable | What it is | Where to get it |
|---|---|---|
OVH_ENDPOINT | OVH API endpoint / legal entity format: one of: ovh-eu | ovh-us | ovh-ca | Not issued anywhere - chosen. It selects the API host: ovh-eu = https://eu.api.ovh.com/v1, ovh-ca = https://ca.api.ovh.com/v1, ovh-us = https://api.us.ovhcloud.com/v1. It must match the entity your account was created under. |
OVH_CLOUD_PROJECT_SERVICE | Public Cloud project ID (service_name) format: 32-character lowercase hex string | OVHcloud Control Panel > Public Cloud > select the project > left menu Project Management > Project settings; the ID is shown as 'Project ID' and also appears in the manager URL after /public-cloud/. Also returned by GET /cloud/project. |
OVH_CLIENT_ID | OAuth2 service account client ID format: opaque service-account client identifier string | OVHcloud Control Panel > click your account name (top right) > My account > API & Identity section > 'Service Accounts' tab > Create a service account. The client ID is displayed on the created account's row and remains visible afterwards. |
OVH_CLIENT_SECRETsecret | OAuth2 service account client secret format: opaque high-entropy string | Same screen as OVH_CLIENT_ID, shown ONCE at creation time only. If lost you must delete the service account and create a new one. Store it in a secret manager, never in ovh.conf inside a repo. |
OVH_APPLICATION_KEYoptional | Legacy application key format: 16-character alphanumeric string | https://www.ovh.com/auth/api/createToken (EU). Only needed if you use legacy AK/AS/CK auth instead of OAuth2. Legacy tokens cannot be constrained by IAM policy - prefer the service account. |
OVH_APPLICATION_SECRETsecretoptional | Legacy application secret format: 32-character alphanumeric string | Displayed once on the token-creation confirmation page at https://www.ovh.com/auth/api/createToken alongside the application key and consumer key. |
OVH_CONSUMER_KEYsecretoptional | Legacy consumer key format: 32-character alphanumeric string | Displayed once on the same confirmation page. It carries the method+path rights you selected (for example GET /cloud/project/*) and the validity you chose. |
OS_AUTH_URL | OpenStack Keystone endpoint format: URL, https://auth.cloud.ovh.net/v3 for ovh-eu | Pre-filled in the openrc.sh downloaded from OVHcloud Control Panel > Public Cloud > project > Project Management > Users & Roles > the user's '...' menu > Download OpenStack's RC file. |
OS_USERNAME | OpenStack project user name format: user-<8 digits> assigned by OVHcloud | OVHcloud Control Panel > Public Cloud > project > Project Management > Users & Roles. The username is generated by OVHcloud when you add the user; you cannot choose it. |
OS_PASSWORDsecret | OpenStack project user password format: generated password string | Shown once in the modal immediately after Users & Roles > Add user. If lost, use the user's '...' menu > 'Generate a new password', which invalidates the old one and any cached openrc. |
OS_PROJECT_ID | OpenStack tenant / project ID format: 32-character lowercase hex string, identical to OVH_CLOUD_PROJECT_SERVICE | Pre-filled in openrc.sh. Older RC files export it as OS_TENANT_ID; export both if a tool complains. |
OS_REGION_NAME | OpenStack region format: uppercase region code, e.g. GRA11, SBG5, EU-WEST-PAR | Chosen when downloading openrc.sh (the dialog asks for a region). Octavia and Nova calls are region-scoped, so re-source a different RC file or override OS_REGION_NAME per region. |
AWS_ACCESS_KEY_ID | Object Storage S3 access key format: 20-character alphanumeric string | OVHcloud Control Panel > Public Cloud > project > Object Storage > S3 users tab, or Users & Roles > a user holding the ObjectStore operator role > '...' > Generate an S3 access key. Equivalent API call: POST /cloud/project/{serviceName}/user/{userId}/s3Credentials. |
AWS_SECRET_ACCESS_KEYsecret | Object Storage S3 secret key format: 40-character base64-ish string | Returned once with the access key from the same screen or API call. Terraform surfaces it as the sensitive attribute secret_access_key on ovh_cloud_project_user_s3_credential. |
Permissions to grant
GET /cloud/project/*API scopeReads quota, regions, flavors, catalog, existing networks, clusters, volumes and current usage before any design decision is made.
โณ Narrow to GET /cloud/project/<projectId>/* so one token cannot enumerate every project on the account.
POST /cloud/project/*API scopeCreates the private network, subnet, gateway, MKS cluster, node pools, Object Storage buckets, project users and S3 credentials.
โณ Narrow to POST /cloud/project/<projectId>/* and keep a separate read-only token for audit and cost-review runs.
PUT /cloud/project/*API scopeUpdates node pool sizing and autoscale bounds, cluster update policy, bucket versioning and lifecycle configuration.
โณ Scope to the project ID; PUT is not needed at all for a plan-only or audit-only run.
DELETE /cloud/project/*API scopeRequired for terraform destroy and for sweeping detached volumes, unattached floating IPs and orphaned snapshots.
โณ Omit entirely from the CI token; grant DELETE only to a short-lived, human-triggered teardown credential.
publicCloudProject:apiovh:*IAM roleadmin consent requiredIAM action wildcard granting the OAuth2 service account full control of Public Cloud resources on the projects targeted by the policy.
โณ Replace the wildcard with the specific actions the run needs, e.g. publicCloudProject:apiovh:quota/get, publicCloudProject:apiovh:kube/create, publicCloudProject:apiovh:network/private/create, publicCloudProject:apiovh:region/gateway/create.
publicCloudProject:apiovh:quota/getIAM roleThe gate in step 2 of the procedure: reads instance, vCore, RAM and GPU quota per region before any HCL is written.
โณ This plus publicCloudProject:apiovh:region/get and publicCloudProject:apiovh:flavor/get is the whole read-only audit profile.
publicCloudProject:apiovh:kube/createIAM roleadmin consent requiredCreates the MKS cluster and its node pools, and retrieves the kubeconfig.
โณ Split cluster lifecycle onto its own service account so an app-deployment credential can never rebuild the control plane.
administratorIAM roleadmin consent requiredPublic Cloud project user role. Needed once, on the human operator, to create other project users and their S3 credentials.
โณ Never attach this to the user whose openrc.sh the automation sources; use a task-scoped role instead.
objectstore_operatorIAM rolePublic Cloud project user role attached to the dedicated S3 user (ovh_cloud_project_user role_names) so the skill can create buckets, set versioning and apply lifecycle policies.
โณ This is already the narrow option - it grants Object Storage only and no compute, network or billing access.
compute_operatorIAM rolePublic Cloud project user role for the OpenStack CLI user: lists flavors and availability zones, manages instances, volumes and floating IPs.
โณ For a read-only inventory sweep, the authentication role alone is enough to obtain a Keystone token for list-only calls in some catalogs; verify before relying on it.
network_operatorIAM rolePublic Cloud project user role required for the Octavia load balancer, listener, pool, health monitor and member operations and for Neutron port/subnet reads.
โณ Pair with network_security_operator only if the design also manages security groups; otherwise leave it off.
Step by step
- 1
Confirm the legal entity and API endpoint before creating anything
Sign in and check which OVHcloud entity owns the account. Set OVH_ENDPOINT to ovh-eu (host https://eu.api.ovh.com/v1), ovh-ca (https://ca.api.ovh.com/v1) or ovh-us (https://api.us.ovhcloud.com/v1). Credentials issued on one entity return 403 on the others, and only ovh-eu satisfies EU sovereignty requirements. The catalogue and price list also differ per entity.
Open in the console / docs โ - 2
Create the Public Cloud project and capture its project ID
OVHcloud Control Panel > Public Cloud > 'Create a project' (or the project selector top-left) > name it > complete payment-method and identity validation. Then Project Management > Project settings and copy 'Project ID' (32 hex chars) into OVH_CLOUD_PROJECT_SERVICE. The same value is the OpenStack tenant ID.
Open in the console / docs โ - 3
Create an OAuth2 service account for API and Terraform auth
Control Panel > account name (top right) > My account > API & Identity > 'Service Accounts' > create one, give it a name and description. Copy the client ID and the client secret - the secret is shown exactly once. Export them as OVH_CLIENT_ID and OVH_CLIENT_SECRET; the ovh/ovh Terraform provider and python-ovh read these automatically, so the provider block needs only endpoint = "ovh-eu".
Open in the console / docs โ - 4
Attach an IAM policy scoping the service account to this project
Control Panel > account name > 'Identity and Access Management (IAM)' > Policies > Add a policy. Set the identity to the service account, the resource group to the Public Cloud project (create a resource group containing only that project), and the actions to the publicCloudProject:apiovh:* set you need. Without a policy the service account has no rights and every call returns 'This call has not been granted'.
Open in the console / docs โ - 5
Optional fallback - create a legacy API token instead
Open the token-creation page, sign in, and add one rights line per method and path: GET /cloud/project/*, POST /cloud/project/*, PUT /cloud/project/*, DELETE /cloud/project/*. Set an explicit validity (never 'Unlimited' for automation). The page then shows the application key, application secret and consumer key once. Legacy tokens cannot be constrained by IAM policy, so only use them where OAuth2 is unsupported.
Open in the console / docs โ - 6
Store credentials in ovh.conf rather than inline in HCL
Create ./ovh.conf, ~/.ovh.conf or /etc/ovh.conf with an [ovh-eu] section containing client_id and client_secret (or application_key / application_secret / consumer_key). python-ovh, the OVH CLI and the Terraform provider all read this file in that order of precedence. Add ovh.conf to .gitignore.
Open in the console / docs โ - 7
Create the OpenStack project user and download openrc.sh
Control Panel > Public Cloud > project > Project Management > 'Users & Roles' > 'Add user'. Select only the roles the run needs (compute_operator and network_operator for Nova/Neutron/Octavia work). Copy the one-time password. Then the user's '...' menu > 'Download OpenStack's RC file', choose the target region, and run: source ./openrc.sh (it prompts for OS_PASSWORD).
Open in the console / docs โ - 8
Create a dedicated S3 user and access key for Object Storage
Users & Roles > 'Add user' with the ObjectStore operator role only > then the user's '...' menu > 'Generate an S3 access key' (also available under Public Cloud > Object Storage > S3 users). Export the pair as AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY, and configure the CLI with the region matching the endpoint host, e.g. aws configure set region gra for https://s3.gra.io.cloud.ovh.net. In Terraform use ovh_cloud_project_user with role_names = ["objectstore_operator"] plus ovh_cloud_project_user_s3_credential.
Open in the console / docs โ - 9
Read quota and raise it before designing around capacity
Call GET /cloud/project/{serviceName}/quota for every target region. Defaults are roughly 20 instances, 20 vCores and 40 GB RAM per region per project, and GPU quota starts at 0. To raise: Control Panel > Public Cloud > project > Quota and regions (also enable 'automatic quota increase' on long-lived projects). Increases are handled manually and can take hours to days.
Open in the console / docs โ - 10
Install and pin the tooling the skill drives
Install Terraform >= 1.5 or OpenTofu, pin ovh = { source = "ovh/ovh", version = "~> 2.0" }, then pip install python-openstackclient python-octaviaclient and install awscli v2, kubectl and jq. Confirm with: terraform version || tofu version; openstack --version; openstack loadbalancer flavor list; aws --version.
Open in the console / docs โ - 11
Attach the vRack only if layer 2 must reach non-Public-Cloud assets
Control Panel > Bare Metal Cloud > Network > vRack > select the vRack > add the Public Cloud project to it (Terraform: ovh_vrack_cloudproject with the vRack service_name and the project ID). Private networking inside a single Public Cloud project does not need a vRack. VLAN IDs (1-4000) must be unique across the whole vRack.
Open in the console / docs โ
Check it worked
TOKEN=$(curl -s -X POST https://www.ovh.com/auth/oauth2/token -d grant_type=client_credentials -d scope=all -d client_id="$OVH_CLIENT_ID" -d client_secret="$OVH_CLIENT_SECRET" | jq -r .access_token) && curl -s -o /dev/null -w 'quota HTTP %{http_code}\n' -H "Authorization: Bearer $TOKEN" "https://eu.api.ovh.com/v1/cloud/project/$OVH_CLOUD_PROJECT_SERVICE/quota"If you hit an error
Error: Client::Forbidden: "This call has not been granted"Cause: The OAuth2 service account has no IAM policy covering the called path, or the legacy consumer key was created without a rights line matching that method and path.
Fix: Add an IAM policy binding the service account to a resource group containing the project with the required publicCloudProject:apiovh:* actions, or re-issue the legacy token with GET/POST/PUT/DELETE on /cloud/project/*. Never widen a production token as a workaround - issue a new scoped one.
{"error":"invalid_client"}Cause: The OAuth2 token request at https://www.ovh.com/auth/oauth2/token was made with a mistyped OVH_CLIENT_ID/OVH_CLIENT_SECRET, a deleted service account, or against the wrong entity's token host.
Fix: Re-copy the client ID, recreate the service account if the secret was lost (it is shown once), and confirm the token host matches OVH_ENDPOINT - ovh-ca and ovh-us use their own auth hosts.
Error: Client::BadRequest: "Invalid signature"Cause: Legacy AK/AS/CK auth only. The application secret is wrong, or the local clock has drifted from the OVH API server time used in the request signature.
Fix: Re-check OVH_APPLICATION_SECRET, then sync the clock (chronyd/w32time). python-ovh calls /auth/time to compensate for drift; hand-rolled signers must do the same.
Error: Client::NotFound: "This service does not exist"Cause: OVH_CLOUD_PROJECT_SERVICE points at a project that does not exist on the entity selected by OVH_ENDPOINT - almost always ovh-eu credentials pointed at an ovh-us/ovh-ca project or vice versa, or a truncated 32-hex ID.
Fix: Run GET /cloud/project with the same credentials and use an ID from that list. Confirm OVH_ENDPOINT before debugging anything else.
The request you have made requires authentication. (HTTP 401)Cause: Keystone rejected the openrc credentials: the project user's password was regenerated, the user was deleted, or OS_PROJECT_ID/OS_TENANT_ID is missing from an older RC file.
Fix: Re-download openrc.sh from Users & Roles, regenerate the password if it was lost, and export both OS_PROJECT_ID and OS_TENANT_ID plus OS_IDENTITY_API_VERSION=3.
Quota exceeded for cores: Requested 8, but already used 16 of 20 cores (HTTP 403)Cause: The node pool or instance request exceeds the region's default Public Cloud quota (roughly 20 instances / 20 vCores / 40 GB RAM per region, GPU quota separate and starting at 0).
Fix: Raise quota from Public Cloud > Quota and regions before applying, or reduce desired_nodes/max_nodes. Quota increases are processed manually, so request them ahead of the apply window.
An error occurred (SignatureDoesNotMatch) when calling the ListObjectsV2 operationCause: The awscli --region value does not match the region embedded in the OVHcloud S3 endpoint host, so SigV4 signs with the wrong scope.
Fix: Keep them aligned: --endpoint-url https://s3.gra.io.cloud.ovh.net with --region gra. Also confirm the key pair belongs to this project - S3 keys are per project.
An error occurred (AccessDenied) when calling the CreateBucket operationCause: The S3 credential belongs to a project user that does not hold the objectstore_operator role, or the target region does not offer the requested storage class (HIGHPERFORMANCE is 1-AZ only, COLDARCHIVE is Paris only).
Fix: Attach objectstore_operator to the user (or create a dedicated one via ovh_cloud_project_user with role_names = ["objectstore_operator"]) and verify class availability for the region before setting --storage-class.
ErrImagePull / Failed to pull image ...: dial tcp: i/o timeoutCause: MKS nodes sit on a private network with no public IP and no managed gateway, so they cannot reach the registry. Cluster creation succeeded, node bootstrap did not.
Fix: Create an ovh_cloud_project_gateway (model s/m/l) on the subnet and add depends_on from the cluster to it. A gateway is mandatory for a multi-zone MKS cluster.
403 ForbiddenCause: Raw 403 from the API host itself, typically ovh-eu credentials sent to https://api.us.ovhcloud.com or https://ca.api.ovh.com.
Fix: Echo OVH_ENDPOINT and the resolved base URL first; the three entities are separate companies and credentials are not portable between them.
Official documentation: https://registry.terraform.io/providers/ovh/ovh/latest/docs โ
API operations this skill uses (20)
Every call the skill makes, linked to its official reference page.
| Operation | Call | Permission | Ref |
|---|---|---|---|
| Obtain an OAuth2 access token (client_credentials) Step 1 of the procedure exchanges the service account for a bearer token used by every subsequent curl against the /cloud API. | POST https://www.ovh.com/auth/oauth2/token (body: grant_type=client_credentials&scope=all&client_id=...&client_secret=...) | Valid OVH_CLIENT_ID / OVH_CLIENT_SECRET; no IAM action required to mint the token itself | docs โ |
| Read project quota per region The mandatory gate in step 2: instance, vCore, RAM and GPU headroom is checked before any HCL is written, because increases are manual and slow. | GET /v1/cloud/project/{serviceName}/quota | publicCloudProject:apiovh:quota/get (GET /cloud/project/*) | docs โ |
| List regions available to the project Distinguishes 3-AZ regions (EU-WEST-PAR, EU-SOUTH-MIL) from 1-AZ regions and Local Zones, and confirms which services each region actually exposes. | GET /v1/cloud/project/{serviceName}/region | publicCloudProject:apiovh:region/get (GET /cloud/project/*) | docs โ |
| List instance flavors and their hourly/monthly prices in a region Confirms the chosen b3/c3/r3/i1/GPU family exists in the target region and returns the live hourly and monthly prices the cost model uses instead of stale list figures. | GET /v1/cloud/project/{serviceName}/flavor?region={region} | publicCloudProject:apiovh:flavor/get (GET /cloud/project/*) | docs โ |
| Create the private network Step 5 lays down one private network with an explicit vlan_id (1-4000, unique across the vRack) spanning every target region, before any compute is created. | POST /v1/cloud/project/{serviceName}/network/private | publicCloudProject:apiovh:network/private/create (POST /cloud/project/*) | docs โ |
| Create a subnet in the private network Allocates the per-region CIDR, DHCP range start/end and gateway, which must not overlap the MKS pod/service CIDRs or any on-premises range. | POST /v1/cloud/project/{serviceName}/network/private/{networkId}/subnet | publicCloudProject:apiovh:network/private/subnet/create (POST /cloud/project/*) | docs โ |
| Create the managed egress gateway Mandatory for a multi-zone MKS cluster and for any node without a public IP; without it nodes join but image pulls time out. | POST /v1/cloud/project/{serviceName}/region/{regionName}/gateway | publicCloudProject:apiovh:region/gateway/create (POST /cloud/project/*) | docs โ |
| Create the Managed Kubernetes (MKS) cluster Creates the cluster with plan free or standard, update_policy, private_network_id, nodes_subnet_id and the pod/service CIDR allocation policy. Plan migration is not supported, so this choice is one-way. | POST /v1/cloud/project/{serviceName}/kube | publicCloudProject:apiovh:kube/create (POST /cloud/project/*) | docs โ |
| Create a node pool pinned to a single availability zone availability_zones takes exactly one zone, so a 3-AZ cluster needs three calls. Sets flavor_name, min/max/desired nodes, autoscale, monthly_billed and anti_affinity. | POST /v1/cloud/project/{serviceName}/kube/{kubeId}/nodepool | publicCloudProject:apiovh:kube/nodepool/create (POST /cloud/project/*) | docs โ |
| Retrieve the cluster kubeconfig Step 11 writes this to ~/.kube/ovh.conf for post-apply verification. Terraform exposes the same value as the sensitive kubeconfig attribute. | POST /v1/cloud/project/{serviceName}/kube/{kubeId}/kubeconfig | publicCloudProject:apiovh:kube/kubeconfig (POST /cloud/project/*) | docs โ |
| Create a scoped Public Cloud project user Creates the least-privilege identity behind openrc.sh and S3 keys, e.g. role_names = ["objectstore_operator"] for the storage credential. | POST /v1/cloud/project/{serviceName}/user | publicCloudProject:apiovh:user/create (POST /cloud/project/*) plus the administrator project role | docs โ |
| Generate S3 credentials for a project user Produces the access_key_id / secret_access_key pair the awscli calls use; the secret is returned once and must be marked sensitive. | POST /v1/cloud/project/{serviceName}/user/{userId}/s3Credentials | publicCloudProject:apiovh:user/s3Credentials/create (POST /cloud/project/*) | docs โ |
| Create and configure an Object Storage bucket Step 9 creates the bucket with versioning, SSE encryption and tags. Object Lock changes here force bucket replacement and destroy every object. | POST /v1/cloud/project/{serviceName}/region/{regionName}/storage | publicCloudProject:apiovh:region/storage/create (POST /cloud/project/*) | docs โ |
| Read current-month usage for the cost model Grounds the cost table in the project's real billed consumption instead of list prices, which the skill requires before quoting any figure. | GET /v1/cloud/project/{serviceName}/usage/current | publicCloudProject:apiovh:usage/current/get (GET /cloud/project/*) | docs โ |
| Attach the Public Cloud project to a vRack Only when layer 2 must be shared with bare metal or Hosted Private Cloud; the VLAN ID then has to be unique across the entire vRack. | POST /v1/vrack/{serviceName}/cloudProject | vrack:apiovh:cloudProject/create (POST /vrack/*) | docs โ |
| List flavors and availability zones via the OpenStack CLI Step 1 cross-checks the OVH catalogue against what Nova actually exposes in the sourced region, including whether the region is genuinely 3-AZ. | CLI source ./openrc.sh && openstack flavor list --all && openstack availability zone list | OpenStack project user with compute_operator (Nova read) | docs โ |
| Create an Octavia load balancer with an explicit flavor Step 8 provisions the LB outside Kubernetes. Omitting --flavor silently yields size S with its ~250 TLS-handshakes/second ceiling, so the flavor ID is resolved first. | CLI openstack loadbalancer flavor list; openstack loadbalancer create --name app-lb --vip-subnet-id $SUBNET_ID --flavor $FLAVOR_ID | OpenStack project user with network_operator (Octavia load-balancer:POST) | docs โ |
| Build the listener, pool, health monitor and members, then bind the floating IP Completes the Octavia object graph the skill emits for non-Kubernetes exposure, defines the health-check semantics used in the runbook, and publishes the VIP. | CLI openstack loadbalancer listener create --protocol TCP --protocol-port 443 app-lb; openstack loadbalancer pool create --lb-algorithm ROUND_ROBIN --listener https --protocol TCP; openstack loadbalancer healthmonitor create --delay 5 --timeout 3 --max-retries 3 --type TCP app-pool; openstack loadbalancer member create --subnet-id $SUBNET_ID --address 10.42.0.11 --protocol-port 8443 app-pool; openstack floating ip set --port $(openstack loadbalancer show app-lb -f value -c vip_port_id) $FIP_ID | OpenStack project user with network_operator (Octavia load-balancer:POST plus Neutron floatingip:update) | docs โ |
| Apply an Object Storage lifecycle policy and write cold objects over the S3 API Step 9 transitions cold data to INFREQUENT / ACTIVEARCHIVE / COLDARCHIVE respecting their 30 / 90 / 180-day minimum retention, and writes known-cold data straight into the target class to avoid a later transition fee. | CLI aws --endpoint-url https://s3.gra.io.cloud.ovh.net --region gra s3api put-bucket-lifecycle-configuration --bucket app-artifacts --lifecycle-configuration file://lifecycle.json | aws --endpoint-url https://s3.gra.io.cloud.ovh.net --region gra s3 cp ./artifact.tar.zst s3://app-artifacts/ --storage-class INFREQUENT | S3 key belonging to a user with objectstore_operator | docs โ |
| Sweep idle capital across volumes, floating IPs and images Cost-model lever 8: detached volumes, unattached floating IPs and orphaned snapshots are the recurring findings in an OVHcloud cost audit. | CLI openstack volume list --status available; openstack floating ip list --status DOWN; openstack image list --private | OpenStack project user with compute_operator and network_operator (read) | docs โ |