Infrastructure as Code (IaC)

Last updated Sep 22, 2026 While Wrangler makes it easy to upload and manage Workers, there are times when you need a more programmatic approach. This could involve using Infrastructure as Code (IaC) tools or interacting directly with the Workers API. Examples include build and deploy scripts, CI/CD pipelines, custom developer tools, and automated testing.

To make this easier, Cloudflare provides SDK libraries for popular languages such as cloudflare-typescript ↗︎ and cloudflare-python ↗︎. For IaC, you can use tools like HashiCorp's Terraform and the Cloudflare Terraform Provider to manage Workers resources.

Below are examples of deploying a Worker using different tools and languages, along with important considerations for managing Workers with IaC.

All of these examples need an account ID and API token (not Global API key) to work.

Workers Bundling

None of the examples below do Workers Bundling. This is usually done with Wrangler or a tool like esbuild ↗︎.

Generally, you'd run this bundling step before applying your Terraform plan or using the API for script upload:

wrangler deploy --dry-run --outdir build

When using Wrangler for building and a different method for uploading, make sure to copy all of your config from wrangler.json into your Terraform config or API request. This is especially important with compatibility_date or flags your script relies on.

Terraform

In this example, you need a local file named my-script.mjs with script content similar to the below examples. Learn more about the Cloudflare Terraform Provider, and refer to the Workers script resource example ↗︎ for all available resource settings.

variable "account_id" {
  default = "replace_me"
}

resource "cloudflare_worker" "my_worker" {
  account_id = var.account_id
  name = "my-worker"
  observability = {
    enabled = true
  }
}

resource "cloudflare_worker_version" "my_worker_version" {
  account_id = var.account_id
  worker_id = cloudflare_worker.my_worker.id
  compatibility_date = "2025-02-21" # Set this to today's date
  main_module = "my-script.mjs"
  modules = [
    {
      name = "my-script.mjs"
      content_type = "application/javascript+module"
      # Replacement (version creation) is triggered whenever this file changes
      content_file = "my-script.mjs"
    }
  ]
}

resource "cloudflare_workers_deployment" "my_worker_deployment" {
  account_id = var.account_id
  script_name = cloudflare_worker.my_worker.name
  strategy = "percentage"
  versions = [{
    percentage = 100
    version_id = cloudflare_worker_version.my_worker_version.id
  }]
}

Notice how you do not have to manage all of these resources in Terraform. For example, you could use just the cloudflare_worker resource and seamlessly use Wrangler or your own deployment tools for Versions or Deployments.

Bindings in Terraform

Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform. In Terraform, bindings are configured differently than in Wrangler. Instead of separate top-level properties for each binding type (like kv_namespaces, r2_buckets, etc.), Terraform uses a single bindings array where each binding has a type property along with type-specific properties.

Below are examples of each binding type and their required properties:

KV Namespace Binding

Bind to a KV namespace for key-value storage:

bindings = [{
  type = "kv_namespace"
  name = "MY_KV"
  namespace_id = "your-kv-namespace-id"
}]

Properties:

  • type: "kv_namespace"
  • name: The variable name for the binding, accessible via env.MY_KV
  • namespace_id: The ID of your KV namespace

R2 Bucket Binding

Bind to an R2 bucket for object storage:

bindings = [{
  type = "r2_bucket"
  name = "MY_BUCKET"
  bucket_name = "my-bucket-name"
}]

Properties:

  • type: "r2_bucket"
  • name: The binding name to access via env.MY_BUCKET
  • bucket_name: The name of your R2 bucket

D1 Database Binding

Bind to a D1 database for SQL storage:

bindings = [{
  type = "d1"
  name = "DB"
  id = "your-database-id"
}]

Properties:

  • type: "d1"
  • name: The binding name to access via env.DB
  • id: The ID of your D1 database

Durable Object Binding

Bind to a Durable Object class:

bindings = [{
  type = "durable_object_namespace"
  name = "MY_DURABLE_OBJECT"
  class_name = "MyDurableObjectClass"
}]

Properties:

  • type: "durable_object_namespace"
  • name: The binding name to access via env.MY_DURABLE_OBJECT
  • class_name: The exported class name of the Durable Object
  • script_name: (Optional) The Worker script that exports this Durable Object class. Omit if the class is defined in the same Worker.

Service Binding

Bind to another Worker for Worker-to-Worker communication:

bindings = [{
  type = "service"
  name = "MY_SERVICE"
  service = "other-worker-name"
}]

Properties:

  • type: "service"
  • name: The binding name to access via env.MY_SERVICE
  • service: The name of the target Worker
  • entrypoint: (Optional) The named entrypoint to bind to

Queue Binding

Bind to a Queue for message passing:

For producing messages:

bindings = [{
  type = "queue"
  name = "MY_QUEUE"
  queue_name = "my-queue"
}]

Properties:

  • type: "queue"
  • name: The binding name to access via env.MY_QUEUE
  • queue_name: The name of your Queue

For consuming messages, configure your Worker as a consumer in the queue resource itself, not via bindings.

Vectorize Binding

Bind to a Vectorize index for vector search:

bindings = [{
  type = "vectorize"
  name = "VECTORIZE_INDEX"
  index_name = "my-index"
}]

Properties:

  • type: "vectorize"
  • name: The binding name to access via env.VECTORIZE_INDEX
  • index_name: The name of your Vectorize index

Workers AI Binding

Bind to Workers AI for AI inference:

bindings = [{
  type = "ai"
  name = "AI"
}]

Properties:

  • type: "ai"
  • name: The binding name to access via env.AI

Hyperdrive Binding

Bind to a Hyperdrive configuration for database connection pooling:

bindings = [{
  type = "hyperdrive"
  name = "HYPERDRIVE"
  id = "your-hyperdrive-config-id"
}]

Properties:

  • type: "hyperdrive"
  • name: The binding name to access via env.HYPERDRIVE
  • id: The ID of your Hyperdrive configuration

VPC Service Binding

Bind to a VPC Service for accessing resources in your private network:

bindings = [{
  type = "vpc_service"
  name = "PRIVATE_API"
  service_id = "your-vpc-service-id"
}]

Properties:

  • type: "vpc_service"
  • name: The binding name to access via env.PRIVATE_API
  • service_id: The ID of your VPC Service (from cloudflare_connectivity_directory_service or the dashboard)

You can create the VPC Service with Terraform using the cloudflare_connectivity_directory_service resource. For a full walkthrough, refer to Configure VPC Services with Terraform.

Analytics Engine Binding

Bind to an Analytics Engine dataset:

bindings = [{
  type = "analytics_engine"
  name = "ANALYTICS"
  dataset = "my_dataset"
}]

Properties:

  • type: "analytics_engine"
  • name: The binding name to access via env.ANALYTICS
  • dataset: The name of your Analytics Engine dataset

Environment Variables

For plain text environment variables, use the plain_text binding type:

bindings = [{
  type = "plain_text"
  name = "MY_VARIABLE"
  text = "my-value"
}]

Properties:

  • type: "plain_text"
  • name: The binding name to access via env.MY_VARIABLE
  • text: The value of the environment variable

Secret Text Binding

For encrypted secrets, use the secret_text binding type:

bindings = [{
  type = "secret_text"
  name = "API_KEY"
  text = var.api_key
}]

Properties:

  • type: "secret_text"
  • name: The binding name to access via env.API_KEY
  • text: The secret value (will be encrypted)

Complete Example

Here's an example combining multiple binding types:

resource "cloudflare_worker_version" "my_worker_version" {
  account_id = var.account_id
  worker_id = cloudflare_worker.my_worker.id
  compatibility_date = "2025-08-06"
  main_module = "worker.js"

modules = [{
    name = "worker.js"
    content_type = "application/javascript+module"
    content_file = "worker.js"
  }]

bindings = [
    {
      type = "kv_namespace"
      name = "MY_KV"
      namespace_id = var.kv_namespace_id
    },
    {
      type = "r2_bucket"
      name = "MY_BUCKET"
      bucket_name = "my-bucket"
    },
    {
      type = "d1"
      name = "DB"
      id = var.d1_database_id
    },
    {
      type = "service"
      name = "AUTH_SERVICE"
      service = "auth-worker"
    },
    {
      type = "plain_text"
      name = "ENVIRONMENT"
      text = "production"
    },
    {
      type = "secret_text"
      name = "API_KEY"
      text = var.api_key
    },
    {
      type = "vpc_service"
      name = "PRIVATE_API"
      service_id = var.vpc_service_id
    }
  ]
}

Cloudflare API Libraries

This example uses the cloudflare-typescript ↗︎ SDK which provides convenient access to the Cloudflare REST API from server-side JavaScript or TypeScript.

```js

!/usr/bin/env -S npm run tsn -T

/* * Create and deploy a Worker * * Docs: * - https://developers.cloudflare.com/workers/versions-and-deployments/ * - https://developers.cloudflare.com/workers/platform/infrastructure-as-code/ * * Prerequisites: * 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/ * 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/ * 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/ * * Environment variables: * - CLOUDFLARE_API_TOKEN (required) * - CLOUDFLARE_ACCOUNT_ID (required) * - CLOUDFLARE_SUBDOMAIN (optional) * * Usage: * Run this script to deploy a simple "Hello World" Worker. * Access it at: my-hello-world-worker.$subdomain.workers.dev /

import { exit } from "node:process";