Infrastructure as Code (IaC) (2)·续2
echo "\nVersion ID: $version_id\n"
Create a deployment for the Worker
deployment_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name/deployments" \ -X POST \ -H "Authorization: Bearer $api_token" \ -H "Content-Type: application/json" \ -d '{ "strategy": "percentage", "versions": [ { "percentage": 100, "version_id": "'$version_id'" } ] }' \ | jq -r '.result.id')
echo "\nDeployment ID: $deployment_id\n"
### multipart/form-data upload API
This API uses [multipart/form-data ↗︎](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/POST) to upload a Worker and will implicitly create a version and deployment. The above API is recommended for direct management of versions and deployments.
```bash
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
For Workers for Platforms, you can upload a User Worker to a dispatch namespace. Note the API endpoint is on /workers/dispatch/namespaces/$DISPATCH_NAMESPACE/scripts/$SCRIPT_NAME.
account_id="replace_me"
api_token="replace_me"
dispatch_namespace="replace_me"
worker_name="my-hello-world-script"
script_content='export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};'
# Create a dispatch namespace
curl https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $api_token" \
-d '{
"name": "'$dispatch_namespace'"
}'
# Upload the Worker
curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces/$dispatch_namespace/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
Python Workers
Python Workers have their own special text/x-python content type and python_workers compatibility flag for uploading using the multipart/form-data API.
curl https://api.cloudflare.com/client/v4/accounts/<account_id>/workers/scripts/my-hello-world-script \
-X PUT \
-H 'Authorization: Bearer <api_token>' \
-F 'metadata={
"main_module": "my-hello-world-script.py",
"bindings": [
{
"type": "plain_text",
"name": "MESSAGE",
"text": "Hello World!"
}
],
"compatibility_date": "$today",
"compatibility_flags": [
"python_workers"
]
};type=application/json' \
-F 'my-hello-world-script.py=@-;filename=my-hello-world-script.py;type=text/x-python' <<EOF
from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response(self.env.MESSAGE)
EOF
Considerations with Durable Objects
Durable Object migrations are applied with deployments. This means you can't bind to a Durable Object in a Version if a deployment doesn't exist i.e. migrations haven't been applied. For example, running this in Terraform will fail the first time the plan is applied:
resource "cloudflare_worker" "my_worker" {
account_id = var.account_id
name = "my-worker"
}
resource "cloudflare_worker_version" "my_worker_version" {
account_id = var.account_id
worker_id = cloudflare_worker.my_worker.id
bindings = [
{
type = "durable_object_namespace"
name = "my_durable_object"
class_name = "MyDurableObjectClass"
}
]
migrations = {
new_sqlite_classes = [
"MyDurableObjectClass"
]
}
# ...version props omitted for brevity
}
resource "cloudflare_workers_deployment" "my_worker_deployment" {
# ...deployment props omitted for brevity
}
To make this succeed, you first have to comment out the durable_object binding block, apply the plan, uncomment it, comment out the migrations block, then apply again. This time the plan will succeed. This also applies to the API or SDKs. This is an example where it makes sense to just manage the cloudflare_worker and/or cloudflare_workers_deployment resources while using Wrangler for build and Version management.
Considerations with Worker Versions
Resource immutability
Worker versions are immutable at the API level, meaning they cannot be updated after creation, only re-created with any desired changes. This means that meaningful changes to the cloudflare_worker_version Terraform resource will always trigger replacement. When the cloudflare_worker_version resource is replaced, a new version with the desired changes is created, but the previous version is not deleted. This ensures the Worker has a complete version history when managed via Terraform. In other words, versions are both immutable and append-only. When the parent cloudflare_worker resource is deleted, all existing versions associated with the Worker are also deleted.
Module Content
Worker version modules support two mutually exclusive ways to provide content:
content_file- Points to a local filecontent_base64- Inline base64-encoded content
In both cases, changes to the underlying content are tracked using the computed content_sha256 attribute. Specifying content using the content_file attribute is preferred in almost all cases, as it avoids storing the content itself in state. Module content may be quite large (up to tens of megabytes), and storing it in state will bloat the state file and negatively affect the performance of Terraform operations. The main use case for the content_base64 attribute is importing the cloudflare_worker_version Terraform resource from the API, discussed below.
Import Behavior
During import, Terraform always populates the content_base64 attribute in state, regardless of the attribute used in your config.
terraform import cloudflare_worker_version.my_worker_version <account_id>/<worker_id>/<version_id>
If your config uses content_file, there will be a mismatch after import (state uses content_base64, config uses content_file). This is expected.
Assuming the content of the local file referenced by content_file matches the imported content and their content_sha256 values are the same, this will result in an in-place update of the cloudflare_worker_version Terraform resource. This should be an in-place update instead of a replacement because the underlying content is not changing (the content_sha256 attribute is the same in both cases), and the resource does not need to be updated at the API level. The only thing that needs to be updated is Terraform state, which will switch from using content_base64 to content_file after the update.
If Terraform instead wants to replace the resource, citing a difference in computed content_sha256 values, then the content of the local file referenced by content_file does not match the imported content and the resource can't be cleanly imported without updating the local file to match the expected API value.
Examples
Using content_file:
resource "cloudflare_worker_version" "content_file_example" {
account_id = var.account_id
worker_id = cloudflare_worker.example.id
main_module = "worker.js"
modules = [{
name = "worker.js"
content_type = "application/javascript+module"
content_file = "build/worker.js"
}]
}
Using content_base64:
resource "cloudflare_worker_version" "content_base64_example" {
account_id = var.account_id
worker_id = cloudflare_worker.example.id
main_module = "worker.js"
modules = [{
name = "worker.js"
content_type = "application/javascript+module"
content_base64 = base64encode("export default { async fetch() { return new Response('Hello world!') } }")
}]
}
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/platform/infrastructure-as-code/#page","headline":"Infrastructure as Code (IaC)","description":"Deploy and manage Cloudflare Workers using Terraform, Pulumi, and the Cloudflare API SDKs.","url":"https://developers.cloudflare.com/workers/platform/infrastructure-as-code/","inLanguage":"en","image":"https://developers.cloudflare.com/workers/platform/infrastructure-as-code/og.png?v=600f367ed79d2a9a","dateModified":"2026-09-22","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Known issues and bugs to be aware of when using Workers. title: Known issues image: https://developers.cloudflare.com/workers/platform/known-issues/og.png?v=a1b1f93e8b0c7037
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt
Use this file to discover all available pages before exploring further.
Docs