Terraform can deploy and manage cloud functions, but it’s surprisingly difficult to keep the deployed code in sync with your source repository.
Let’s see a simple Cloud Function in action, deployed and managed by Terraform. Imagine we have a small HTTP-triggered function written in Python that just echoes back a query parameter.
# main.py
import functions_framework
@functions_framework.http
def hello_query(request):
"""HTTP Cloud Function that echoes a query parameter."""
name = request.args.get("name", "World")
return f"Hello {name}!"
Now, here’s how you’d define this function in Terraform. We’ll use Google Cloud Functions as our example, but the principles apply to AWS Lambda and Azure Functions as well.
# main.tf
provider "google" {
project = "your-gcp-project-id"
region = "us-central1"
}
resource "google_storage_bucket" "source_bucket" {
name = "your-gcp-project-id-cloud-function-source"
location = "US"
uniform_bucket_level_access = true
}
resource "google_storage_bucket_object" "source_archive" {
name = "source.zip"
bucket = google_storage_bucket.source_bucket.name
source = "source.zip" # This zip file must exist locally
}
resource "google_cloudfunctions_function" "hello_query_function" {
name = "hello-query"
description = "My function"
runtime = "python311"
entry_point = "hello_query"
trigger_http = true
source_archive_bucket = google_storage_bucket.source_bucket.name
source_archive_object = google_storage_bucket_object.source_archive.name
environment_variables = {
MY_VAR = "some_value"
}
depends_on = [
google_storage_bucket_object.source_archive
]
}
output "function_url" {
description = "The URL of the deployed function."
value = google_cloudfunctions_function.hello_query_function.https_trigger_url
}
Before running terraform apply, you’d need to create source.zip containing your main.py and any other dependencies (like requirements.txt). A simple zip source.zip main.py will do for this example.
When you run terraform apply, Terraform uploads source.zip to a GCS bucket and then tells Google Cloud Functions to deploy a new version using that code. The function_url output will give you the endpoint to test it.
The problem arises when you change your main.py. Terraform, by default, doesn’t track changes within the source.zip. If you update main.py and run zip source.zip main.py again, terraform plan will show no changes because the source.zip file’s metadata (like its modification time) hasn’t changed in a way Terraform monitors by default. To force a re-deployment, you need to make Terraform aware of the change. A common pattern is to use a trigger argument that changes whenever the source code changes, often derived from the file’s hash.
# Modified main.tf for source code changes
resource "google_storage_bucket_object" "source_archive" {
name = "source.zip"
bucket = google_storage_bucket.source_bucket.name
source = "source.zip"
# This ensures Terraform detects changes in the source.zip file
content_hash = filemd5("source.zip")
}
resource "google_cloudfunctions_function" "hello_query_function" {
name = "hello-query"
description = "My function"
runtime = "python311"
entry_point = "hello_query"
trigger_http = true
source_archive_bucket = google_storage_bucket.source_bucket.name
source_archive_object = google_storage_bucket_object.source_archive.name
environment_variables = {
MY_VAR = "some_value"
}
# Explicitly depend on the object to ensure it's uploaded before function deployment
depends_on = [
google_storage_bucket_object.source_archive
]
}
By adding content_hash = filemd5("source.zip") to the google_storage_bucket_object resource, Terraform calculates the MD5 hash of your source.zip. When you update main.py and re-zip, the hash of source.zip changes, which Terraform detects, triggering a re-deployment of the Cloud Function.
You can also manage function configuration like memory, timeout, and environment variables directly within the google_cloudfunctions_function resource. For example, to allocate 512MB of memory and set a 60-second timeout:
resource "google_cloudfunctions_function" "hello_query_function" {
# ... other arguments ...
available_memory_mb = 512
timeout = 60
# ... other arguments ...
}
However, managing the source code lifecycle can become complex. Many teams opt for a GitOps approach where CI/CD pipelines handle zipping and uploading the code, and Terraform only manages the function’s infrastructure configuration. This decouples code deployment from infrastructure changes. Terraform would then reference a specific version of the code artifact (e.g., a versioned zip file in GCS or an image in a container registry).
The most common pitfall is forgetting to update the source.zip or not having Terraform properly detect changes to it, leading to deployed code that’s out of sync with your local or repository code.
The next hurdle is managing IAM permissions for your cloud functions, ensuring they can access other cloud resources securely.