terraform apply publishes the reviewed version to Serval.
What Terraform manages
Theserval_workflow resource covers a workflow’s code and core settings:
id and has_unpublished_changes are read-only.
Set these in the app instead. Terraform updates leave them unchanged:
- Who can run the workflow
- Service Catalog listing and the Service Catalog description
- The access package whose connections the workflow uses. Workflows created by Terraform start in the team’s default package.
- Approvals
Leave out the provider’s
execution_scope attribute. Serval no longer reads it. Set who can run a workflow in the app.Before you begin
You need:- Terraform 1.11 or later
- Remote state storage that everyone running Terraform shares, such as an S3 bucket or HCP Terraform
-
Two API keys, both restricted to the teams Terraform manages:
- An apply key with the
workflows:read,workflows:write, andworkflows:deploypermission bundles.workflows:deployis only needed to publish. Without it, applies save drafts. - A plan key with only
workflows:read, for runningterraform planon pull requests.
- An apply key with the
- The UUID of each team. Open Team Settings → Your Team and select Copy team UUID.
Set up the provider
1
Declare the provider
use_lockfile does this for S3; other backends have their own setting.Pin the minor version. The provider is pre-1.0, so a new minor release can include breaking changes. ~> 0.26.0 accepts only patch releases.2
Provide credentials
The provider reads the API key from environment variables and exchanges it for an access token on each run:If your organization is in the EU region, also set the API URL:Data residency lists the API URL for each region. Keep credentials out of
.tf files and store them as CI secrets.Define a workflow
Keep each workflow’s code in its own file and load it withfile():
file() passes the code through unchanged. If you write code inline with <<-EOT instead, Terraform reads ${...} and %{...} as its own syntax. Escape them as $${...} and %%{...} so TypeScript template literals reach Serval intact.
Bring existing workflows under Terraform
Import workflows that already exist instead of recreating them. An import keeps the workflow’s ID, version history, and run history.1
Pull the code and IDs
Run
serval pull for the team. Each workflow.yaml it writes includes the workflow’s id, and index.ts holds the code to copy into your repository.2
Add an import block next to each resource
3
Plan and apply
Run
terraform plan and adjust the configuration until the plan shows only imports. Set is_published to match the workflow: true for a live workflow, false for one that has never been published. Importing an unpublished workflow with is_published = true publishes it on apply. Apply the plan, then delete the import blocks.Review changes in pull requests
A typical setup runsterraform plan on every pull request and terraform apply after merge, so changes reach Serval only after review.
A pull request can change the files a CI job runs, so give pull-request jobs only the read-only plan key. Keep the apply key in a GitHub environment whose deployment branches are limited to main. Jobs from other branches can’t read its secrets. Anyone who can open a pull request can still use the plan key to read workflows on its teams, so limit it to the teams this repository manages.
This GitHub Actions example uses:
- Repository secrets
SERVAL_PLAN_CLIENT_IDandSERVAL_PLAN_CLIENT_SECRETfor the plan key - An environment named
servalwith secretsSERVAL_CLIENT_IDandSERVAL_CLIENT_SECRETfor the apply key
serval-apply concurrency group runs one apply at a time. Pull-request plans aren’t in it, so they never cancel a waiting apply. Plans skip the state lock with -lock=false, and -lock-timeout lets an apply wait out any other lock holder instead of failing. Protect main so changes merge only after review.
How applies behave
- Publishing: With
is_published = true, each apply that changes the workflow publishes a new version, which appears in version history. Withis_published = false, the change is saved as a draft. It doesn’t unpublish a live workflow, and on a live workflow every plan showsis_publishedchanging, so keep ittrueonce a workflow is published. - Failed publishing: If Serval rejects the code, the apply fails before anything changes. If publishing fails for another reason, such as a connection the workflow can’t use, the new code can stay behind as a draft while the live version is unchanged.
- Edits made in Serval: Terraform compares your files with the workflow’s latest draft. Changes made in the app or with Catalyst show up in the next plan, and an apply replaces them with the code in your repository. Make changes to Terraform-managed workflows in the repository.
- Removing a resource: Deleting a
serval_workflowblock, or runningterraform destroy, deletes the workflow in Serval. You can restore it from Team Settings → Recently deleted for 30 days. It comes back as an unpublished draft, so import it again before managing it with Terraform. - Changing
team_id: Terraform deletes the workflow and creates a new one with a new ID. Anything that refers to the old ID stops working.

