Skip to content
Cloudflare Docs

Infrastructure as Code (IaC)

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 Cloudflare 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, including cloudflare-typescript and cloudflare-python. For IaC, you can use tools like HashiCorp's Terraform and the Cloudflare Terraform Provider to create and manage Workers resources.

Below are examples of deploying a Worker using different tools and languages, along with important considerations for managing Workers with IaC. Each example shows how to upload script content and metadata, which may differ depending on the approach. For a detailed definition of script uploads, see the Upload Worker Module API docs here.

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:

Terminal window
wrangler deploy --dry-run -outdir build

Then you'd reference the bundled script like build/index.js.

Make sure to copy all of your config from wrangler.json into your Terraform config or API request. This is especially important for 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 here, and see an example with all the Workers script resource settings here.

variable "account_id" {
default = "replace_me"
}
resource "cloudflare_worker" "my_worker" {
account_id = var.account_id
script_name = "my-worker"
}
resource "cloudflare_workers_version" "my_worker_version" {
account_id = var.account_id
script_id = cloudflare_workers_script.my_worker.id
compatibility_date = "$today"
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"
content_sha256 = filesha256("my-script.mjs")
}
]
# Versions can only be created, never updated or destroyed
lifecycle {
create_before_destroy = true
prevent_destroy = true
}
}
resource "cloudflare_workers_deployment" "my_worker_deployment" {
account_id = var.account_id
script_id = cloudflare_workers_script.my_worker.id
strategy = "percentage"
versions = [{
percentage = 100
version_id = cloudflare_workers_version.my_worker_version.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.

#!/usr/bin/env -S npm run tsn -T
/**
* Create a Worker with a Version and Deployment
*
* Docs
* - https://developers.cloudflare.com/workers/configuration/versions-and-deployments/
* - https://developers.cloudflare.com/workers/platform/infrastructure-as-code/
*
* Generate an API token:
* https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
* (Not Global API Key!)
*
* Find your account id:
* https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
*
* Find your workers.dev subdomain:
* https://developers.cloudflare.com/workers/configuration/routing/workers-dev/
*
* Set these environment variables:
* - CLOUDFLARE_API_TOKEN
* - CLOUDFLARE_ACCOUNT_ID
* - CLOUDFLARE_SUBDOMAIN
*/
import Cloudflare from "cloudflare";
const apiToken = process.env["CLOUDFLARE_API_TOKEN"] ?? "";
if (!apiToken) {
throw new Error("Please set envar CLOUDFLARE_API_TOKEN");
}
const accountID = process.env["CLOUDFLARE_ACCOUNT_ID"] ?? "";
if (!accountID) {
throw new Error("Please set envar CLOUDFLARE_ACCOUNT_ID");
}
const subdomain = process.env["CLOUDFLARE_SUBDOMAIN"] ?? "";
const client = new Cloudflare({
apiToken,
});
async function main() {
const workerName = "my-hello-world-worker";
const scriptFileName = `${workerName}.mjs`;
// Workers Scripts use ES Module Syntax
// https://blog.cloudflare.com/workers-javascript-modules/
const scriptContent = `
export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};
`;
/**
* Create a Worker and set non-versioned settings like observability
*/
const worker = await client.workers.create(workerName, {
account_id: accountID,
subdomain: {
enabled: subdomain ? true : false,
},
observability: {
enabled: true,
},
});
/**
* Create the first version of the Worker
* This is where code and bindings are defined and can be different between versions
*/
const version = await client.workers.versions.create(worker.id, {
account_id: accountID,
main_module: scriptFileName,
compatibility_date: new Date().toISOString().split("T")[0],
bindings: [
{
type: "plain_text",
name: "MESSAGE",
text: "Hello World!",
},
],
modules: [
{
name: scriptFileName,
content_type: "application/javascript+module",
content_base64: Buffer.from(scriptContent).toString("base64"),
},
],
});
/**
* Create a deployment and point all traffic to the version we created
* Triggers that hit fetch() in the Worker (ie. HTTP requests from workers.dev, routes, and custom domains)
* split requests between one or multiple versions by deployments
*/
const deployment = await client.workers.scripts.deployments.create(
worker.name,
{
account_id: accountID,
strategy: "percentage",
versions: [
{
percentage: 100,
version_id: version.id,
},
],
},
);
console.log(JSON.stringify(deployment, null, 2));
if (subdomain) {
console.log(
`${workerName} is live at: ${workerName}.${subdomain}.workers.dev`,
);
} else {
console.log(
"Setup a route, custom domain, or workers.dev subdomain to access this Worker.",
);
}
}
main();

See the same example in Python in the cloudflare-python SDK.

Cloudflare REST API

Open a terminal or create a shell script to upload a Worker and manage versions and deployments with curl. Workers scripts are Javascript ES Modules, but we also support Python Workers (open beta) and Rust Workers.

Terminal window
account_id="replace_me"
api_token="replace_me"
worker_name="my-hello-world-worker"
worker_script_base64=$(echo '
export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};
' | base64)
# Create the Worker
worker_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d "{
"name": '"$worker_name"'
}" \
| jq -r '.result.id')
# Upload the Worker's first version
version_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/$worker_id/versions" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d "{
'compatibility_date': '$today',
'main_module': '"$worker_name".mjs',
'modules': [
{
'name': '"$worker_name"'.mjs',
'content_type': 'application/javascript+module',
'content_base64': '"$worker_script_base64"'
}
],
'bindings': [
{
'type': 'plain_text',
'name': 'MESSAGE',
'text': 'Hello World!'
}
]
}" \
| jq -r '.result.id')
# Create a deployment for the Worker
curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/$worker_id/deployments" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d "{
'strategy': 'percentage',
'versions': [
{
'percentage': 100,
'version_id': '"$version_id"'
}
]
}"

multipart/form-data upload API

This API uses multipart/form-data to upload a Worker and will implicitly create a version and deployment. The above API is recommended for direct management of versions and deployments.

Terminal window
account_id="replace_me"
api_token="replace_me"
worker_name="my-hello-world-script"
script_content='export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};'
# Upload the Worker
curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name" \
-X PUT \
-H "Authorization: Bearer $api_token" \
-F "metadata={
'main_module': '"$worker_name".mjs',
'bindings': [
{
'type': 'plain_text',
'name': 'MESSAGE',
'text': 'Hello World!'
}
],
'compatibility_date': '$today'
};type=application/json" \
-F "$worker_name.mjs=@-;filename=$worker_name.mjs;type=application/javascript+module" <<EOF
$script_content
EOF