Terraform Provider
Manage Private Cloud Director infrastructure as code with the first-party PCD Terraform provider. Install, authenticate, and provision your first resources.
The Private Cloud Director (PCD) Terraform provider (platform9/pcd) is the first-party way to manage PCD as code. It is published on the public Terraform Registry and works with any standard Terraform (or OpenTofu) workflow.
Information
The PCD Terraform provider is currently in beta. It is published at a 0.x version, so resources and attributes can change between releases. Pin a provider version in your configuration and review the release notes before you upgrade.
Overview
The provider manages the services that PCD exposes: Identity Service, Compute Service, Networking Service (including QoS and quotas), Persistent Storage Service, Image Library Service, Load Balancing (Octavia/OVN), DNS (Designate), and Key Management (Barbican). It also manages PCD's own cluster blueprints and host configuration and roles, declared as code.
In this guide, you'll install the provider, authenticate against your PCD environment, and create your first resources with Terraform.
Requirements
PCD
PCD 2026 April release or later
Terraform
1.0 or later (provider protocol 6)
OpenTofu
Any release supporting protocol 6
Install the Provider
Declare the provider in your Terraform configuration and pin a compatible version:
terraform {
required_providers {
pcd = {
source = "platform9/pcd"
version = "~> 0.1"
}
}
}Run terraform init. Terraform downloads the provider from the registry and verifies its GPG signature automatically.
Authenticate
Password, token, and application-credential authentication are all supported, as is sourcing credentials from a clouds.yaml file (cloud / OS_CLOUD).
Every argument can instead be supplied through the standard OS_* environment variables (OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_PROJECT_NAME, OS_REGION_NAME, OS_INSECURE, and so on), so an existing RC file works unchanged.
Quickstart
This minimal example authenticates, then creates a private network and subnet:
Runnable, self-contained examples for every resource and data source, including import.sh files for importable resources, are in the provider repository under examples/.
Day 1 Configuration
Day 1 configuration stands up a new region: a cluster blueprint, host networking, a host cluster, and the roles that onboard a host into it. These resources configure PCD's own control plane. The order below matters: most steps either name a resource created earlier or otherwise depend on one being ready first. The complete, runnable version of these examples, applied and destroyed end to end on Community Edition, is the provider's Community Edition guide, with its source in examples/complete/community-edition/.
These examples assume the host is already prepared and authorized in PCD. See pcdctl if it isn't yet; the host_id variable below is that host's resmgr UUID. The host records that UUID in /etc/pf9/host_id.conf. The other ways to find it, and every other ID the provider asks for, are in Look Up IDs for Import below.
Create a Volume Type
A volume type is the name tenants choose when they create a volume. Its volume_backend_name extra spec routes those volumes to a storage backend that the cluster blueprint declares in storage_backends_json, and the blueprint's image_library_storage names a volume type as well. PCD validates that the type exists when the blueprint is saved, so create the type first. The nfs in volume_backend_name below is the backend name: it matches the top-level key of the backend declared in the blueprint that follows, and if you rename one, rename both.
Define the Cluster Blueprint
PCD keeps one cluster blueprint per region. storage_backends_json declares the region's Persistent Storage Service backends. The example below declares a single NFS backend.
storage_backends_json has two levels of keys. The top-level key is the backend name (nfs here): on the host it becomes volume_backend_name, which is what a volume type's volume_backend_name must equal. The key under it names one driver configuration for that backend (nfs-primary here; both names are yours to choose): it is what the persistent-storage role's backends list selects, and it becomes the backend section on the host. That configuration carries driver and config. Write boolean options as booleans, not quoted strings; a quoted "true" never validates and the host keeps converging. driver is one of the built-in driver identifiers (NFS, LVM, HitachiISCSI, and so on) or the full class path of a custom driver, and config holds that driver's own keys, the same ones the UI shows under Infrastructure > Cluster Blueprint > Persistent Storage Connectivity > Add Volume Backend Configuration. For other drivers, see Certified Block Storage Drivers & Configurations.
Sensitive State
storage_backends_json carries the backend driver's credentials and is stored in Terraform state as plain text. Use a remote backend with encryption at rest and restrict who can read it, rather than a local state file or plain version control.
If the region already has a blueprint, don't create a new one: see the section below on managing an existing blueprint to import and manage it instead.
Configure and Assign Host Networking
pcd_host_config maps each traffic type on a host to a network interface, and network_labels maps a physical-network label to one of those same interfaces. A later provider network binds to the host through that label, so pick a name (physnet1 below) and reuse it consistently.
Create the Cluster and Onboard the Host
pcd_cluster is the host cluster a hypervisor joins; VM high availability and auto-rebalancing are declared on it, so they're part of the region's definition from the start rather than a setting you turn on later. pcd_host_cluster_role then onboards the host by assigning it a cluster role: PCD expands each cluster role into its granular roles and computes their settings from the blueprint and the host configuration above, instead of you setting each one by hand. That makes it a different, higher-level resource than pcd_host_role in the table below, which assigns one granular role directly and suits only roles that need no computed settings.
Setting wait_until_converged = true blocks each role's apply until the host reports it converged. Do this for every role here: the Day 2 resources that follow (images, instances, volumes) need a hypervisor, image library, and storage backend that are actually ready.
Each role explicitly depends on the host configuration assignment. Nothing above references it by attribute, so Terraform can't infer the order on its own, and assigning a cluster role before the host has its network configuration produces an inconsistent host. The persistent-storage role's backends list names a driver configuration from the blueprint's storage_backends_json: the second-level key (nfs-primary here), not the backend name (nfs) above it. Assigning the role is what turns that configuration into a running storage service on the host. Naming the top-level key instead leaves the service with no backend, and the host cannot converge.
Day 2 Configuration
With the region converged, Day 2 configuration adds the resources a workload runs on: a network, an image, a flavor, an instance, and a volume. These are the tenant-facing services described in the Overview above.
Configure Tenant Networking
This example creates a flat provider network on the physnet1 label from the host configuration above, so instances land directly on that network.
The network depends on the hypervisor role directly, because the physnet1 label only exists on a host once that role has converged. Nothing in the network's own attributes references the role, so Terraform needs the explicit dependency.
A security group with SSH and ICMP access completes the tenant-facing setup:
Add an Image and a Flavor
This example uses CirrOS, a minimal test image commonly used to validate an image library end to end. Point image_source_url (or local_file_path) at your own image for anything you plan to run for real.
The image depends on the image-library role directly: uploads need a working image-library host to receive them, and image_source_url isn't a reference Terraform can order on its own.
The upload goes to the image-library host itself, on port 9494, which is how the UI uploads images too. The machine running Terraform must reach the host on that port; if it cannot, set endpoint_overrides = { image = "https://<reachable-address>:9494/v2/" } in the provider block.
Boot an Instance with an Attached Volume
The instance boots from the image and flavor above onto the workload subnet, then gets a Persistent Storage Service volume attached to it:
The instance only references the network by ID, not the subnet, so it depends on the subnet directly: PCD needs an actual subnet, not just a bare network, to assign the instance an address. The volume depends on the persistent-storage role the same way: nothing in its attributes points at that role, but the backend needs a running service on the host before it can accept a new volume.
Manage PCD-Native Infrastructure
PCD keeps one cluster blueprint per region, so a region that already runs has a blueprint to import rather than create. The import ID is the blueprint's name.
With Terraform 1.5 or later, an import block and terraform plan -generate-config-out=generated.tf write the resource block for you, which is the quickest way to learn the shape of a blueprint you have only configured in the UI. The sensitive storage_backends_json comes out as null, which is what you want: the provider keeps the backends the region already has.
Destroying an imported blueprint deletes it from PCD. To stop managing it without deleting it, run terraform state rm pcd_cluster_blueprint.region.
pcd_cluster_blueprint
The region's cluster blueprint: virtual-network segmentation, DNS domain, image library and VM storage, and the Persistent Storage Service backends. One per region.
pcd_cluster
A cluster, the unit hypervisors join. VM high availability and auto-rebalancing are set here.
pcd_host_config
A host's mapping of each traffic type (management, VM console, tunneling, image library, live migration) to a network interface, plus physical-network labels.
pcd_host_config_assignment
Attaches a host configuration to a host.
pcd_host_cluster_role
Onboards a host by assigning it a cluster role (hypervisor, image-library, persistent-storage, dns); PCD computes the granular role settings from the blueprint and host configuration.
pcd_host_role
Assigns one granular role (for example pf9-ostackhost-neutron) directly. The low-level API under pcd_host_cluster_role; reserve it for roles that take no settings.
A read-only pcd_cluster_blueprint data source is also available.
Look Up IDs for Import
Import IDs are assigned by PCD. pcdctl prints them; source the pcdctl RC file from Settings > API Access first, and on Community Edition also set OS_INSECURE=true for its self-signed certificate. (pcdctl config set configures the host onboarding commands; it does not authenticate these.)
pcd_blockstorage_volume_type
Volume type UUID
pcdctl volume type list
pcd_blockstorage_volume
Volume UUID
pcdctl volume list
pcd_images_image
Image UUID
pcdctl image list
pcd_compute_flavor
Flavor UUID
pcdctl flavor list
pcd_compute_instance
Instance UUID
pcdctl server list
pcd_networking_network, pcd_networking_subnet
Network or subnet UUID
pcdctl network list, pcdctl subnet list
pcd_networking_secgroup
Security group UUID
pcdctl security group list
pcd_cluster_blueprint
Blueprint name
GET /resmgr/v2/blueprint (below)
pcd_cluster
Cluster name
pcdctl aggregate list
pcd_host_config
Host configuration UUID
GET /resmgr/v2/hostconfigs (below)
pcd_host_config_assignment, pcd_host_cluster_role, pcd_host_role
<host-uuid>/<config-or-role>
Host UUID, as below
The host UUID is not the ID that pcdctl hypervisor list shows. The host records it in /etc/pf9/host_id.conf; once the host has the hypervisor role, pcdctl hypervisor show <hypervisor-id> -c service_host and pcdctl compute service list --service nova-compute -c Host -c Zone print it too.
Host configurations, blueprints, and clusters are read from the resource manager API with a token from pcdctl:
The full per-resource table is in the provider's Importing guide.
Full Resource Reference
Per-resource and per-data-source documentation, including every argument, attribute, and import ID, is generated from the provider schema and published on the Terraform Registry:
Two guides there cover this page's workflows end to end:
Community Edition guide: a runnable zero-to-VM configuration for a Community Edition region.
Importing guide: the import ID and lookup command for every importable resource.
The Registry is the source of truth for the reference; this page covers concepts and getting started.
Support and Source
Source and issues: github.com/platform9/terraform-provider-pcd
Examples:
examples/in the repositoryLicense: Mozilla Public License 2.0. Portions are ported from
terraform-provider-openstack(MPL-2.0) and carry provenance comments.
Related Pages
PCD CLI - pcdctl: the pcdctl equivalent for imperative, scripted operations.
Certified Block Storage Drivers & Configurations: the driver keys for
storage_backends_json.
Last updated
Was this helpful?
