Provision an OVH VPS + Domain with Terraform

Setting up a VPS and domain name on OVH using Terraform proved challenging due to poor documentation and a brittle provisioning process, despite the cost-effective resources offered. The experience highlighted the need for better documentation and more reliable provisioning options.

For a side project I wanted to set up a small VPS and Domain Name on OVH. Using OVH you get a lot for your money compared to traditional Big Cloud providers, or even VPS providers like DigitalOcean.

The entry-level VPS at OVH Canada in 2026 is between C$6 and C$8. For that price you get: 4 vCPU, 8GB RAM, 75 GB SSD with a generous networking allowance. Hard to beat.

In order to provision that instance, I wanted to use Terraform to more easily manage my infra as code. This is a simple hobby project and the setup is likely overkill.

Provisioning a VPS

The good news: There is an OVH Terraform Provider.
The bad news: The provisioning options are poorly documented and provisioning process seems brittle.

terraform {
  required_providers {
    ovh = {
      source = "ovh/ovh"
    }
  }
}

resource "my_super_super" "vps" {
  display_name = "My super VPS"

  plan = [
    {
      duration       = "P1M"             # P1M = 1 month
      plan_code      = "vps-2025-model1" # VPS 4 vCPU 8 GB RAM 75 GB disk
      pricing_mode   = "default"
      configuration = [
        {
          label = "vps_datacenter"
          value = "BHS" # BHS = Beauharnois, Canada
        },
        {
          label = "vps_os"
          value = "Ubuntu 24.04"
        }
      ]
    },
    {
      duration     = "P1M"
      # Automated Daily backup (7 days)
      plan_code    = "option-auto-backup-2025-7-model1"
      pricing_mode = "default"
    }
  ]

  public_ssh_key = file("~/.ssh/id_ed25519.pub")
}

My main.tf used to provision a VPS.

Nowhere in the module documentation, nor in the OVH website, you will easily find those codes. You also need to infer that the automatic backup option is another item in the plan.

To discover these codes, you will need to navigate their Catalog API.

Another downside is the time it takes to provision new resources... and that it didn't work in the first try. I had to re-run it again.

ovh_vps.vps: Still creating... [04m40s elapsed]
ovh_vps.vps: Still creating... [04m50s elapsed]
ovh_vps.vps: Still creating... [05m00s elapsed]
ovh_vps.vps: Still creating... [05m10s elapsed]
╷
│ Error: Provider returned invalid result object after apply
│ 
│ After the apply operation, the provider still indicated an unknown value for ovh_vps.vps.cluster. All values must be known after apply, so this is always a bug in the provider
│ and should be reported in the provider's own repository. Terraform will still save the other known object values in the state.
╵

Provisioning a Domain Name for the VPS

OVH is also a domain registrar and sells domain names. Perfect, I can also add a domain for my pet project.

resource "ovh_domain_name" "docvault" {
  domain_name = "example.com"
  ovh_subsidiary = "CA"
}

Domain name creation is fairly simple (main.tf)

ovh_domain_name.docvault: Creating...
ovh_vps.vps: Creating...
ovh_domain_name.docvault: Still creating... [00m10s elapsed]
ovh_domain_name.docvault: Still creating... [00m20s elapsed]
(...)
ovh_domain_name.docvault: Still creating... [20m40s elapsed]
ovh_domain_name.docvault: Still creating... [20m50s elapsed]
ovh_domain_name.docvault: Creation complete after 20m54s [id=docvault.click]

Domain name creation took a while to complete.

Now the interesting part is that I would like to point my new domain name to my freshly provisioned VPS. Great, this is where Terraform excels: the output from the VPS provisioning can be used as input to the domain name configuration.

However, the OVH provider does not quite work like that. The ovh_vps resource will not returned networking information. Instead it should be fetched using vps_info. Additionally IPv4 and IPv6 are returned under the same IP Addresses array.


# The ovh_vps resource does not return the IP address. 
# We use this data source to fetch the assigned IP addresses of the VPS.
data "ovh_vps" "vps_info" {
  service_name = ovh_vps.vps.service_name
}

locals {
  # Look at all the IPs for this VPS, keep only the ones that consist of numbers and dots (IPv4), 
  # and give me the first one found.
  vps_ipv4 = [for ip in data.ovh_vps.vps_info.ips : ip if can(regex("^[0-9.]+$", ip))][0]
}

resource "ovh_domain_zone_record" "root" {
  zone      = ovh_domain_name.docvault.domain_name
  subdomain = ""
  fieldtype = "A"
  ttl       = 3600
  target    = local.vps_ipv4
}

Retrieving the VPS IP address and create an A record on the domain name.

Recovering from failed VPS provisioning

Terraform indicated an error with the VPS provisioning, but I still got an email confirming my order.

However in order to complete the domain name configuration I had to refetch information for my resources:

terraform untaint ovh_vps.vps
Resource instance ovh_vps.vps has been successfully untainted.
terraform refresh
ovh_vps.vps: Refreshing state...
ovh_domain_name.docvault: Refreshing state... [id=example.com]
data.ovh_vps.vps_info: Reading...
data.ovh_vps.vps_info: Read complete after 3s [id=vps-xxxxx.vps.ovh.ca]

Outputs:

vps_ip = "158.xx.xxx.xxx"
vps_service_name = "vps-xxxxx.vps.ovh.ca"

So what about now?

Honestly, this has not been a great experience. Should I have known that in advance, I would simply have manually created my server and domain from the UI.

At least I learned something.