How to Scale AI Agents with Reusable Terraform Modules
Once you are running more than one or two AI agents, copy-pasting Terraform between projects stops scaling. The fix is the same as for any infrastructure: capture the agent once as a reusable module, parameterise what differs, and roll it out consistently across environments and teams - with version pinning and a CI/CD pipeline doing the applies.
This tutorial shows you how to turn a ChatBotKit agent into a module and deploy it at scale.
What You'll Learn
By the end of this tutorial, you will be able to:
- Package an AI agent (bot, skillset, abilities) as a reusable Terraform module
- Parameterise the agent with input variables and expose outputs
- Deploy the module across environments using Terraform workspaces
- Stand up many agents from one configuration
- Pin the provider version and store state remotely
- Automate plan/apply with GitHub Actions
Prerequisites
Before starting, make sure you have:
- A ChatBotKit account with an API key
- Terraform version 1.0 or higher installed
- Familiarity with Terraform modules, variables, and state
Estimated time: 30-40 minutes
Step 1: Package the Agent as a Module
A module is just a directory of Terraform files with inputs and outputs. Create modules/agent/ and capture the agent there, with everything that varies between deployments exposed as variables.
modules/agent/variables.tf:
variable "name" {
description = "Display name of the agent"
type = string
}
variable "backstory" {
description = "System prompt that defines the agent's behaviour"
type = string
}
variable "model" {
description = "The model the agent uses"
type = string
default = "claude-4.5-sonnet"
}
variable "meta" {
description = "Metadata applied to the agent (environment, team, etc.)"
type = map(string)
default = {}
}
modules/agent/main.tf:
terraform {
required_providers {
chatbotkit = {
source = "chatbotkit/chatbotkit"
}
}
}
resource "chatbotkit_skillset" "abilities" {
name = "${var.name} Abilities"
description = "Toolset for ${var.name}"
}
resource "chatbotkit_skillset_ability" "web_search" {
skillset_id = chatbotkit_skillset.abilities.id
name = "search_web"
description = "Search the web for current information"
instruction = <<-EOT
```search
query: $[query! ys|the search query to find information on the web]
```
EOT
}
resource "chatbotkit_bot" "agent" {
name = var.name
backstory = var.backstory
model = var.model
skillset_id = chatbotkit_skillset.abilities.id
meta = var.meta
}
modules/agent/outputs.tf:
output "bot_id" {
description = "The ID of the agent"
value = chatbotkit_bot.agent.id
}
output "skillset_id" {
description = "The ID of the agent's skillset"
value = chatbotkit_skillset.abilities.id
}
Now the agent's shape lives in one place. Anything that should be configurable per deployment is an input; anything other configurations need to reference is an output.
Step 2: Use the Module
A root configuration consumes the module. Pin the provider version and configure a remote backend so a team (and CI) can share state safely.
main.tf:
terraform {
required_providers {
chatbotkit = {
source = "chatbotkit/chatbotkit"
version = "~> 1.5"
}
}
backend "s3" {
bucket = "my-tf-state"
key = "chatbotkit/agents.tfstate"
region = "us-east-1"
}
}
provider "chatbotkit" {}
variable "environment" {
type = string
default = "development"
}
module "support_agent" {
source = "./modules/agent"
name = "Support Agent (${var.environment})"
backstory = "You are a helpful customer-support assistant. Be friendly and resolve issues on first contact."
meta = {
managed_by = "terraform"
environment = var.environment
team = "customer-success"
}
}
output "support_bot_id" {
value = module.support_agent.bot_id
}
Step 3: Deploy Across Environments with Workspaces
Workspaces give each environment its own state from the same configuration. Combined with the environment variable, you get isolated dev / staging / production agents:
terraform workspace new development
terraform apply -var "environment=development"
terraform workspace new production
terraform apply -var "environment=production"
Switch between them with terraform workspace select <name>. Each workspace tracks its own resources, so a change in development never touches production.
Step 4: Stand Up Many Agents at Once
Because the agent is a module, you can instantiate it many times in one configuration - for example a fleet of department agents. Drive them from a single map so adding one is a data change:
variable "agents" {
type = map(object({
backstory = string
}))
default = {
"Sales Agent" = { backstory = "You are a sales assistant. Qualify leads and answer product questions." }
"Support Agent" = { backstory = "You are a support assistant. Resolve issues quickly and clearly." }
"HR Agent" = { backstory = "You are an HR assistant. Answer policy questions for employees." }
}
}
module "fleet" {
source = "./modules/agent"
for_each = var.agents
name = each.key
backstory = each.value.backstory
}
output "fleet_bot_ids" {
value = { for k, m in module.fleet : k => m.bot_id }
}
Note:
for_eachworks for instantiating modules within a single account like this. To deploy into separate accounts (one per customer), use provider aliases andrun_asinstead - see How to Deploy Multi-Tenant AI Agents with Terraform.
Step 5: Automate with CI/CD
With state remote and the provider pinned, hand applies over to a pipeline so every change is planned, reviewed, and applied consistently. A minimal GitHub Actions workflow:
name: Deploy ChatBotKit Agents
on:
push:
branches: [main]
paths: ['terraform/**']
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: terraform
- name: Terraform Apply
run: terraform apply -auto-approve
working-directory: terraform
env:
CHATBOTKIT_API_KEY: ${{ secrets.CHATBOTKIT_API_KEY }}
For safety, run terraform plan on pull requests and reserve apply for merges to main. Store CHATBOTKIT_API_KEY as an encrypted CI secret.
Best Practices for Scale
- Version everything. Pin the provider (
version = "~> 1.5") and commit the lock file so every environment resolves the same provider. - One module, many callers. Keep the agent definition in the module; let roots only supply inputs. Improving the agent is a single edit.
- Remote, isolated state. Use a remote backend and a separate state (workspace or key) per environment or tenant, so blast radius stays contained.
- Tag with
meta. Stampenvironment,team, andmanaged_byso resources are easy to find and audit. - Review plans. Gate
applybehind a reviewed plan in CI.
Next Steps
- How to Deploy Multi-Tenant AI Agents with Terraform - one isolated agent per customer sub-account
- How to Build an AI Agent Declaratively with Terraform - the single-agent basics
- Terraform Provider documentation - resources, data sources, and CI/CD
Capturing an agent as a module turns "deploy an agent" into a repeatable, reviewable operation - the foundation for running many agents, across many environments, without the configuration sprawl.