|
| 1 | +--- |
| 2 | +title: Configure with Terraform |
| 3 | +pcx_content_type: how-to |
| 4 | +description: Learn how to manage VPC Services using the Cloudflare Terraform provider. |
| 5 | +sidebar: |
| 6 | + order: 2 |
| 7 | +--- |
| 8 | + |
| 9 | +VPC Services can be managed as infrastructure using the [`cloudflare_connectivity_directory_service`](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/connectivity_directory_service) resource in the [Cloudflare Terraform provider](/terraform/). |
| 10 | + |
| 11 | +This maps directly to the [connectivity directory](/api/resources/connectivity/subresources/directory/subresources/services/) — the underlying API that the dashboard and Wrangler CLI also use to create and manage VPC Services. The same [VPC Service configuration fields](/workers-vpc/configuration/vpc-services/#vpc-service-configuration) (type, host, ports, tunnel ID) apply regardless of how the service is created. |
| 12 | + |
| 13 | +:::note |
| 14 | +Requires Cloudflare Terraform provider v5.13.0 or later. |
| 15 | +::: |
| 16 | + |
| 17 | +## VPC Service resource |
| 18 | + |
| 19 | +The `cloudflare_connectivity_directory_service` resource creates a VPC Service in the connectivity directory. Each resource corresponds to one VPC Service entry that a Worker can bind to. |
| 20 | + |
| 21 | +### Hostname-based configuration |
| 22 | + |
| 23 | +When using a hostname, provide `host.hostname` with a `resolver_network` block. This parallels the hostname-based [JSON configuration example](/workers-vpc/configuration/vpc-services/#configuration-example). |
| 24 | + |
| 25 | +```tf |
| 26 | +resource "cloudflare_connectivity_directory_service" "my_private_api" { |
| 27 | + account_id = var.account_id |
| 28 | + name = "my-private-api" |
| 29 | + type = "http" |
| 30 | + http_port = 80 |
| 31 | + https_port = 443 |
| 32 | +
|
| 33 | + host = { |
| 34 | + hostname = "internal-api.example.com" |
| 35 | + resolver_network = { |
| 36 | + tunnel_id = var.tunnel_id |
| 37 | + } |
| 38 | + } |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +To use a custom DNS resolver within your private network, add `resolver_ips`: |
| 43 | + |
| 44 | +```tf |
| 45 | +resource "cloudflare_connectivity_directory_service" "my_private_api" { |
| 46 | + account_id = var.account_id |
| 47 | + name = "my-private-api" |
| 48 | + type = "http" |
| 49 | +
|
| 50 | + host = { |
| 51 | + hostname = "internal-api.example.com" |
| 52 | + resolver_network = { |
| 53 | + tunnel_id = var.tunnel_id |
| 54 | + resolver_ips = ["10.0.0.53"] |
| 55 | + } |
| 56 | + } |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +### IP-based configuration |
| 61 | + |
| 62 | +When using IP addresses, provide `host.ipv4` and/or `host.ipv6` with a `network` block. This parallels the IP-based [JSON configuration example](/workers-vpc/configuration/vpc-services/#configuration-example). |
| 63 | + |
| 64 | +```tf |
| 65 | +resource "cloudflare_connectivity_directory_service" "my_private_api" { |
| 66 | + account_id = var.account_id |
| 67 | + name = "my-private-api" |
| 68 | + type = "http" |
| 69 | + http_port = 8080 |
| 70 | + https_port = 8443 |
| 71 | +
|
| 72 | + host = { |
| 73 | + ipv4 = "10.0.1.50" |
| 74 | + ipv6 = "fe80::1" |
| 75 | + network = { |
| 76 | + tunnel_id = var.tunnel_id |
| 77 | + } |
| 78 | + } |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +### Port configuration |
| 83 | + |
| 84 | +Ports are optional and default to 80 (HTTP) and 443 (HTTPS). To enforce a single scheme, provide only one of `http_port` or `https_port`. Refer to [VPC Service configuration](/workers-vpc/configuration/vpc-services/#vpc-service-configuration) for how scheme enforcement and port behavior work. |
| 85 | + |
| 86 | +## Workers binding configuration |
| 87 | + |
| 88 | +Once a VPC Service exists, bind it to a Worker using the `vpc_service` binding type in the `bindings` array of a `cloudflare_worker_version` resource. This is equivalent to the [`vpc_services` array in Wrangler configuration](/workers-vpc/configuration/vpc-services/#workers-binding-configuration). |
| 89 | + |
| 90 | +```tf |
| 91 | +resource "cloudflare_worker_version" "my_worker_version" { |
| 92 | + account_id = var.account_id |
| 93 | + worker_id = cloudflare_worker.my_worker.id |
| 94 | + compatibility_date = "2025-02-21" # Set this to today's date |
| 95 | + main_module = "worker.js" |
| 96 | +
|
| 97 | + modules = [{ |
| 98 | + name = "worker.js" |
| 99 | + content_type = "application/javascript+module" |
| 100 | + content_file = "build/worker.js" |
| 101 | + }] |
| 102 | +
|
| 103 | + bindings = [{ |
| 104 | + type = "vpc_service" |
| 105 | + name = "PRIVATE_API" |
| 106 | + service_id = cloudflare_connectivity_directory_service.my_private_api.service_id |
| 107 | + }] |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +Multiple VPC Service bindings can be added to the same Worker: |
| 112 | + |
| 113 | +```tf |
| 114 | +bindings = [ |
| 115 | + { |
| 116 | + type = "vpc_service" |
| 117 | + name = "PRIVATE_API" |
| 118 | + service_id = cloudflare_connectivity_directory_service.api.service_id |
| 119 | + }, |
| 120 | + { |
| 121 | + type = "vpc_service" |
| 122 | + name = "PRIVATE_DATABASE" |
| 123 | + service_id = cloudflare_connectivity_directory_service.database.service_id |
| 124 | + } |
| 125 | +] |
| 126 | +``` |
| 127 | + |
| 128 | +The Worker code accesses each binding through `env.PRIVATE_API.fetch()` and `env.PRIVATE_DATABASE.fetch()`, as described in the [Workers Binding API](/workers-vpc/api/). |
| 129 | + |
| 130 | +For more details on managing Workers and bindings with Terraform, refer to [Workers Infrastructure as Code](/workers/platform/infrastructure-as-code/). |
| 131 | + |
| 132 | +## Data sources |
| 133 | + |
| 134 | +The Terraform provider includes data sources for reading existing VPC Services without managing their lifecycle. |
| 135 | + |
| 136 | +### Look up a single VPC Service |
| 137 | + |
| 138 | +```tf |
| 139 | +data "cloudflare_connectivity_directory_service" "existing" { |
| 140 | + account_id = var.account_id |
| 141 | + service_id = "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +This is useful for binding to a VPC Service that is managed outside of your Terraform configuration (for example, created through the dashboard or Wrangler CLI). |
| 146 | + |
| 147 | +### List VPC Services |
| 148 | + |
| 149 | +```tf |
| 150 | +data "cloudflare_connectivity_directory_services" "all_http" { |
| 151 | + account_id = var.account_id |
| 152 | + type = "http" |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +## Resource schema reference |
| 157 | + |
| 158 | +```tf |
| 159 | +resource "cloudflare_connectivity_directory_service" "example" { |
| 160 | + # Required |
| 161 | + account_id = "your-account-id" # Account identifier |
| 162 | + name = "my-private-api" # Human-readable name |
| 163 | + type = "http" # Service type (only "http" supported) |
| 164 | +
|
| 165 | + # Optional |
| 166 | + http_port = 80 # HTTP port (default: 80) |
| 167 | + https_port = 443 # HTTPS port (default: 443) |
| 168 | +
|
| 169 | + host = { |
| 170 | + # Use hostname OR ipv4/ipv6, not both |
| 171 | +
|
| 172 | + # Option A: Hostname-based |
| 173 | + hostname = "internal-api.example.com" |
| 174 | + resolver_network = { |
| 175 | + tunnel_id = "tunnel-uuid" # Required — Cloudflare Tunnel ID |
| 176 | + resolver_ips = ["10.0.0.53"] # Optional — custom DNS resolver IPs |
| 177 | + } |
| 178 | +
|
| 179 | + # Option B: IP-based |
| 180 | + # ipv4 = "10.0.1.50" # IPv4 address |
| 181 | + # ipv6 = "fe80::1" # IPv6 address |
| 182 | + # network = { |
| 183 | + # tunnel_id = "tunnel-uuid" # Required — Cloudflare Tunnel ID |
| 184 | + # } |
| 185 | + } |
| 186 | +
|
| 187 | + # Read-only (computed by the API) |
| 188 | + # id — Terraform resource ID |
| 189 | + # service_id — VPC Service ID (use this for Worker bindings) |
| 190 | + # created_at — Creation timestamp |
| 191 | + # updated_at — Last update timestamp |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +For the full schema, refer to the [Terraform registry documentation](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/connectivity_directory_service). |
0 commit comments