Terraform
Terraform ↗ is an infrastructure as code software tool that allows you to deploy services from different providers using a standardized configuration syntax. When creating a Terraform configuration file, you define the final state of the configuration rather than the step-by-step procedure. This allows you to easily deploy, modify, and manage your Tunnels alongside your other infrastructure.
In this guide, you will use Terraform to deploy:
- A Google Cloud Project (GCP) virtual machine that runs an HTTP test server
- A Cloudflare Tunnel that makes the server available over the Internet
- A Cloudflare Access policy that defines who can connect to the server
To complete the following procedure, you will need:
- A Google Cloud Project ↗
- A zone on Cloudflare
- Enabled one-time PIN login or integrated an identity provider
Refer to the Terraform installation guide ↗ for your operating system.
-
Install the gcloud CLI ↗ so that Terraform can interact with your GCP account.
-
Authenticate with the CLI by running:
Terminal window gcloud auth application-default login
Create an API token so that Terraform can interact with your Cloudflare account. At minimum, your token should include the following permissions:
Type | Item | Permission |
---|---|---|
Account | Cloudflare Tunnel | Edit |
Account | Access: Apps and Policies | Edit |
Zone | DNS | Edit |
Terraform functions through a working directory that contains configuration files. You can store your configuration in multiple files or just one — Terraform will evaluate all of the configuration files in the directory as if they were in a single document.
-
Create a folder for your Terraform configuration:
Terminal window mkdir cloudflare-tf -
Change into the directory:
Terminal window cd cloudflare-tf
The following variables will be passed into your GCP and Cloudflare configuration.
-
In your configuration directory, create a
.tf
file:Terminal window touch variables.tf -
Open the file in a text editor and copy and paste the following:
# GCP variablesvariable "gcp_project_id" {description = "Google Cloud Platform (GCP) project ID"type = string}variable "zone" {description = "Geographical zone for the GCP VM instance"type = string}variable "machine_type" {description = "Machine type for the GCP VM instance"type = string}# Cloudflare variablesvariable "cloudflare_zone" {description = "Domain used to expose the GCP VM instance to the Internet"type = string}variable "cloudflare_zone_id" {description = "Zone ID for your domain"type = string}variable "cloudflare_account_id" {description = "Account ID for your Cloudflare account"type = stringsensitive = true}variable "cloudflare_email" {description = "Email address for your Cloudflare account"type = stringsensitive = true}variable "cloudflare_token" {description = "Cloudflare API token"type = stringsensitive = true}
-
In your configuration directory, create a
.tfvars
file:Terminal window touch terraform.tfvarsTerraform will automatically use these variables if the file is named
terraform.tfvars
, otherwise the variable file will need to be manually passed in. -
Add the following variables to
terraform.tfvars
. Be sure to modify the example with your own values.cloudflare_zone = "example.com"cloudflare_zone_id = "023e105f4ecef8ad9ca31a8372d0c353"cloudflare_account_id = "372e67954025e0ba6aaa6d586b9e0b59"cloudflare_email = "user@example.com"cloudflare_token = "y3AalHS_E7Vabk3c3lX950F90_Xl7YtjSlzyFn_X"gcp_project_id = "testvm-123"zone = "us-central1-a"machine_type = "e2-medium"
You will need to declare the providers ↗ used to provision the infrastructure.
-
In your configuration directory, create a
.tf
file:Terminal window touch providers.tf -
Add the following providers to
providers.tf
. Therandom
provider is used to generate a tunnel secret.terraform {required_providers {cloudflare = {source = "cloudflare/cloudflare"version = ">= 5.3.0"}google = {source = "hashicorp/google"}random = {source = "hashicorp/random"}}required_version = ">= 1.2"}# Providersprovider "cloudflare" {api_token = var.cloudflare_token}provider "google" {project = var.gcp_project_id}provider "random" {}terraform {required_providers {cloudflare = {source = "cloudflare/cloudflare"version = ">= 4.40.0, < 5.0.0"}google = {source = "hashicorp/google"}random = {source = "hashicorp/random"}}required_version = ">= 1.2"}# Providersprovider "cloudflare" {api_token = var.cloudflare_token}provider "google" {project = var.gcp_project_id}provider "random" {}
The following configuration will modify settings in your Cloudflare account.
-
In your configuration directory, create a
.tf
file:Terminal window touch Cloudflare-config.tf -
Add the following resources to
Cloudflare-config.tf
:# Generates a 32-byte secret for the tunnel.resource "random_bytes" "tunnel_secret" {byte_length = 32}# Creates a new remotely-managed tunnel for the GCP VM.resource "cloudflare_zero_trust_tunnel_cloudflared" "gcp_tunnel" {account_id = var.cloudflare_account_idname = "Terraform GCP tunnel"tunnel_secret = random_bytes.tunnel_secret.base64}# Reads the token used to run the tunnel on the server.data "cloudflare_zero_trust_tunnel_cloudflared_token" "gcp_tunnel_token" {account_id = var.cloudflare_account_idtunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id}# Creates the CNAME record that routes http_app.${var.cloudflare_zone} to the tunnel.resource "cloudflare_dns_record" "http_app" {zone_id = var.cloudflare_zone_idname = "http_app"content = "${cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id}.cfargotunnel.com"type = "CNAME"ttl = 1proxied = true}# Configures tunnel with a public hostname route for clientless access.resource "cloudflare_zero_trust_tunnel_cloudflared_config" "gcp_tunnel_config" {tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.idaccount_id = var.cloudflare_account_idconfig = {ingress = [{hostname = "http_app.${var.cloudflare_zone}"service = "http://httpbin:8080"},{service = "http_status:404"}]}}# (Optional) Routes internal IP of GCP instance through the tunnel for private network access using WARP.resource "cloudflare_zero_trust_tunnel_cloudflared_route" "example_tunnel_route" {account_id = var.cloudflare_account_idtunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.idnetwork = google_compute_instance.http_server.network_interface.0.network_ipcomment = "Example tunnel route"}# Creates a reusable Access policy.resource "cloudflare_zero_trust_access_policy" "allow_emails" {account_id = var.cloudflare_account_idname = "Allow email addresses"decision = "allow"include = [{email = {email = var.cloudflare_email}},{email_domain = {domain = "@example.com"}}]}# Creates an Access application to control who can connect to the public hostname.resource "cloudflare_zero_trust_access_application" "http_app" {account_id = var.cloudflare_account_idtype = "self_hosted"name = "Access application for http_app.${var.cloudflare_zone}"domain = "http_app.${var.cloudflare_zone}"policies = [{id = cloudflare_zero_trust_access_policy.allow_emails.idprecedence = 1}]}# Generates a 32-byte secret for the tunnel.resource "random_bytes" "tunnel_secret" {byte_length = 32}# Creates a new remotely-managed tunnel for the GCP VM.resource "cloudflare_zero_trust_tunnel_cloudflared" "gcp_tunnel" {account_id = var.cloudflare_account_idname = "Terraform GCP tunnel"secret = random_bytes.tunnel_secret.base64}# Creates the CNAME record that routes http_app.${var.cloudflare_zone} to the tunnel.resource "cloudflare_record" "http_app" {zone_id = var.cloudflare_zone_idname = "http_app"content = "${cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.cname}"type = "CNAME"proxied = true}# Configures tunnel with a public hostname route for clientless access.resource "cloudflare_zero_trust_tunnel_cloudflared_config" "gcp_tunnel_config" {tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.idaccount_id = var.cloudflare_account_idconfig {ingress_rule {hostname = "${cloudflare_record.http_app.hostname}"service = "http://httpbin:8080"}ingress_rule {service = "http_status:404"}}}# (Optional) Route internal IP of GCP instance through the tunnel for private network access using WARP.resource "cloudflare_zero_trust_tunnel_route" "example_tunnel_route" {account_id = var.cloudflare_account_idtunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.idnetwork = google_compute_instance.http_server.network_interface.0.network_ipcomment = "Example tunnel route"}# Creates an Access application to control who can connect to the public hostname.resource "cloudflare_zero_trust_access_application" "http_app" {account_id = var.cloudflare_account_idname = "Access application for http_app.${var.cloudflare_zone}"domain = "http_app.${var.cloudflare_zone}"}# Creates a (legacy) Access policy for the Access application.resource "cloudflare_zero_trust_access_policy" "allow_emails" {application_id = cloudflare_zero_trust_access_application.http_app.idaccount_id = var.cloudflare_account_idname = "Example policy for http_app.${var.cloudflare_zone}"precedence = "1"decision = "allow"include {email = [var.cloudflare_email]}}
To learn more about these resources, refer to the Cloudflare provider documentation ↗.
The following configuration defines the specifications for the GCP virtual machine and configures a startup script to run upon boot.
-
In your configuration directory, create a
.tf
file:Terminal window touch GCP-config.tf -
Add the following content to
GCP-config.tf
:# OS the server will usedata "google_compute_image" "image" {family = "ubuntu-minimal-2004-lts"project = "ubuntu-os-cloud"}# GCP Instance resourceresource "google_compute_instance" "http_server" {name = "test"machine_type = var.machine_typezone = var.zonetags = []boot_disk {initialize_params {image = data.google_compute_image.image.self_link}}network_interface {network = "default"access_config {//Ephemeral IP}}// Optional config to make instance ephemeral/* scheduling {preemptible = trueautomatic_restart = false} */// Pass the tunnel token to the GCP server so that the server can install and run the tunnel upon startup.metadata_startup_script = templatefile("./install-tunnel.tftpl",{tunnel_token = data.cloudflare_zero_trust_tunnel_cloudflared_token.gcp_tunnel_token.token})}# OS the server will usedata "google_compute_image" "image" {family = "ubuntu-minimal-2004-lts"project = "ubuntu-os-cloud"}# GCP Instance resourceresource "google_compute_instance" "http_server" {name = "test"machine_type = var.machine_typezone = var.zonetags = []boot_disk {initialize_params {image = data.google_compute_image.image.self_link}}network_interface {network = "default"access_config {//Ephemeral IP}}// Optional config to make instance ephemeral/* scheduling {preemptible = trueautomatic_restart = false} */// Pass the tunnel token to the GCP server so that the server can install and run the tunnel upon startup.metadata_startup_script = templatefile("./install-tunnel.tftpl",{tunnel_token = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.tunnel_token})}
The following script will install cloudflared
and run the tunnel as a service. This example also installs a lightweight HTTP application that you can use to test connectivity.
-
In your configuration directory, create a Terraform template file:
Terminal window touch install-tunnel.tftpl -
Open the file in a text editor and copy and paste the following bash script:
Terminal window # Script to install Cloudflare Tunnel and Docker resources# Docker configurationcd /tmpsudo apt-get install software-properties-common# Retrieving the docker repository for this OScurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"# The OS is updated and docker is installedsudo apt update -y && sudo apt upgrade -ysudo apt install docker docker-compose -y# Add the HTTPBin application and run it on localhost:8080.cat > /tmp/docker-compose.yml << "EOF"version: '3'services:httpbin:image: kennethreitz/httpbinrestart: alwayscontainer_name: httpbinports:- 8080:80cloudflared:image: cloudflare/cloudflared:latestrestart: alwayscontainer_name: cloudflaredcommand: tunnel run --token ${tunnel_token}EOFcd /tmpsudo docker-compose up -d
To deploy the configuration files:
-
Initialize your configuration directory:
Terminal window terraform init -
Preview everything that will be created:
Terminal window terraform plan -
Apply the configuration:
Terminal window terraform apply
It may take several minutes for the GCP instance and tunnel to come online. You can view your new tunnel, Access application, and Access policy in Zero Trust ↗. The new DNS records are available in the Cloudflare dashboard ↗.
-
In Networks > Tunnels, verify that your tunnel is active.
-
In Access > Applications, verify that your Cloudflare email is allowed by the Access policy.
-
From any device, open a browser and go to
http_app.<CLOUDFLARE_ZONE>
(for example,http_app.example.com
).You will see the Access login page if you have not recently logged in.
-
Log in with your Cloudflare email.
You should see the HTTPBin ↗ homepage.
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Products
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark