Migrating Kubernetes Terraform Resources from Non-Versioned to Versioned Resources in Coder
Last updated: July 7, 2026
Overview
This article provides step-by-step instructions for migrating existing Coder workspaces that use Kubernetes Terraform resources from their non-versioned resource types (e.g., kubernetes_deployment) to their versioned counterparts (e.g., kubernetes_deployment_v1).
This migration capability was added in Coder versions 2.27.10, 2.28.7, and 2.29.2 through the introduction of the --no-build flag for state management operations, which allows you to update workspace state without triggering a workspace build.
Related Pull Requests:
coder/coder#21374 — adds the
--no-buildflagcoder/coder#22201 — ensures a stop build is triggered before a start build upon Update and Restart for dynamic parameter templates (first available in 2.31.1, and in 2.32.x, 2.33.x, 2.34.x)
Important Background: Dynamic Parameters and the Update and Restart Flow
Before following this procedure, it is critical to understand how the "Update and Restart" flow interacts with Dynamic Parameters, because this behavior directly affects whether the migration will preserve or destroy your resources.
Starting in Coder 2.25.0, Dynamic Parameters are automatically enabled for all new templates. Even if your template does not use any Dynamic Parameter features (no
form_type, no conditionalcounton parameters, no cross-parameter references), it still runs through the Dynamic Parameters engine.In versions prior to 2.31.1, there was a bug where "Update and Restart" would not trigger a stop build for workspaces built from templates instantiated as Dynamic Parameters. Because Dynamic Parameters are enabled by default from 2.25.0+, virtually all templates hit this bug.
Starting with PR #22201 (first shipped in 2.31.1, and included in all 2.32.x / 2.33.x / 2.34.x releases), "Update and Restart" for Dynamic Parameter workspaces now runs two builds:
A stop build that reconciles Terraform state against the currently-deployed template version (the version the workspace was previously built with, not the active template version).
A start build that applies the active template version.
Why this matters for the migration
After you follow the procedure below and use coder state push --no-build to upload the migrated state, there is intentionally a diff between:
The state you just pushed (which references versioned resource types like
kubernetes_deployment_v1), andThe currently-deployed template version associated with the workspace (which still references the non-versioned types like
kubernetes_deployment).
If the user then clicks Update and Restart on a running workspace on version 2.31.1+, the stop build runs against the previously-deployed template version, sees the diff, and — because a stop transition runs terraform destroy — deletes the resources associated with that drift, including PVCs.
You must therefore use one of the two safe procedures below when performing the "Update Workspaces" step:
Option A (recommended): Stop the workspace before running through this KB, so no stop transition runs when the user clicks Update and Restart.
Option B: Leave the workspace running, complete the KB steps, and then trigger a start build directly via the Coder API while explicitly forcing the active template version (see Step 13).
Prerequisites
Before beginning this migration process, ensure you have:
Coder deployment upgraded to version 2.27.10, 2.28.7, 2.29.2, or later (2.31.1+ strongly recommended — see background above)
Coder CLI upgraded to match the server version
Access to pull workspace state and templates
Terraform CLI installed on your local system
Access to modify and push templates
Migration Steps
Step 1: Upgrade Your Coder Deployment
Upgrade your Coder deployment to a version that includes the --no-build flag:
2.27.10
2.28.7
2.29.2 (or later)
If you are on 2.31.1 or later, be especially careful about the "Update and Restart" behavior described in the background section — you must use Option A or Option B in Step 13.
Step 2: (Recommended) Stop the Workspace
If you plan to use Option A, stop the workspace now:
coder stop <workspace-name>This ensures that when the user later clicks Update and Restart, only a start transition runs — no stop build, so no terraform destroy against the drifted state.
If you plan to use Option B (API-triggered start build against a running workspace), you can skip this step.
Step 3: Pull the Workspace State
Pull the current state of the workspace you want to migrate and save it locally:
coder state pull <workspace-name> > <workspace-name>.tfstateStep 4: Verify Current Resources
List the resources in the state file to confirm they are using non-versioned resource types:
terraform state list -state=<workspace-name>.tfstateReview the output to identify all non-versioned Kubernetes resources that need migration.
Step 5: Pull the Template
Download the corresponding template from your Coder deployment:
coder templates pull <template-name> --org <org>Step 6: Update Template Configuration
Navigate to the template directory and modify the Terraform configuration files:
Change resource names to include the version suffix:
Before:
kubernetes_deploymentAfter:
kubernetes_deployment_v1
Apply this change to all non-versioned Kubernetes resources (deployments, PVCs, secrets, services, etc.).
Update all references to these resources throughout your Terraform configuration to use the new versioned names.
Step 7: Remove Old Resources from State
Remove each non-versioned resource from the state file:
terraform state rm -state=<workspace-name>.tfstate kubernetes_deployment.mainRepeat this command for all non-versioned Kubernetes resources (PVCs, secrets, services, etc.).
Step 8: Verify Resource Removal
Confirm that all non-versioned Kubernetes resources have been removed from the state:
terraform state list -state=<workspace-name>.tfstateEnsure no non-versioned resources remain in the output.
Step 9: Import Resources with Versioned Names
While in the template directory, import each resource using its new versioned name:
terraform import -state=<workspace-name>.tfstate \ -var="use_kubeconfig=true" \ -var="namespace=coder" \ 'kubernetes_persistent_volume_claim_v1.home' \ coder/coder-13e44247-8bce-4000-99c2-edd243fd9856-homeImportant notes:
Adjust variables, resource addresses, and resource IDs to match your specific deployment.
Repeat this import command for all Kubernetes resources being migrated.
For resources using meta-arguments like
count, include the index in the resource address:✅ Correct:
kubernetes_deployment_v1.main[0]❌ Incorrect:
kubernetes_deployment_v1.mainThe
[0]index indicates you're importing the first instance of the resource created bycount.
Step 10: Verify Version Compatibility
Before proceeding, confirm that both your CLI and server are running compatible Coder versions (2.27.10, 2.28.7, 2.29.2, or later). The next step requires the --no-build flag, which is only available in these versions.
Step 11: Push Updated State
Push the modified state back to Coder without triggering a workspace build:
coder state push --no-build <workspace-name> <workspace-name>.tfstateThe --no-build flag ensures that the state is updated without triggering a workspace build or recreation of resources.
Step 12: Update the Template
Push the updated template configuration to Coder:
coder templates push <template>Step 13: Update Workspaces
At this point, there is a diff between the pushed state (versioned resources) and the currently-deployed template version associated with the workspace (non-versioned resources). On Coder 2.31.1+, clicking "Update and Restart" on a running workspace will trigger a stop build against the old template version and destroy the drifted resources (including PVCs).
Choose one of the following options:
Option A: Stop the workspace, then Update and Restart (Recommended)
If you followed Step 2, the workspace is already stopped. Then have the user click Update and Restart in the UI, or run:
coder start <workspace-name>Because the workspace was already stopped, no stop transition runs, only a start transition using the active template version, so the drifted resources are not destroyed.
Option B: Force a start build against the active template version via the API
If the workspace must remain running, do not use coder start (since there is a case where coder start checks if the workspace is already running and bails out) or the UI's "Update and Restart" button. Instead, trigger a start build directly against the Coder API while explicitly setting template_version_id to the active template version. This bypasses the stop build entirely and forces the active version (a plain start build would otherwise reuse the previously-deployed template version).
The Coder CLI (coder start) cannot be used for this because it refuses to start a workspace that is already running.
# Get workspace ID
WS_ID=$(curl -s -H "Coder-Session-Token: $CODER_SESSION_TOKEN" \
"$CODER_URL/api/v2/users/me/workspace/$WORKSPACE_NAME" | jq -r '.id')
# Get the active template version ID
coder templates list -c "active version id" -c "name"
# Set ACTIVE_VER to the active version ID from the output above
ACTIVE_VER="<active-version-id>"
# POST a start build with the active template version explicitly set
curl -X POST "$CODER_URL/api/v2/workspaces/$WS_ID/builds" \
-H "Coder-Session-Token: $CODER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"transition\":\"start\",\"template_version_id\":\"$ACTIVE_VER\"}"Because only a start transition runs (and it is pinned to the active template version), the drifted resources are not destroyed and the workspace picks up the new versioned resource types.
Important Considerations
Backup: Consider backing up your workspace state files before beginning the migration.
Testing: Test this migration process on a non-production workspace first.
Downtime: Plan for potential workspace downtime during the migration (unavoidable in Option A, avoidable in Option B).
Dynamic Parameters interaction: On Coder 2.31.1+, never allow users to click "Update and Restart" on a running workspace after
coder state push --no-builduntil you have executed Option A or Option B. Doing so will run a stop build against the previously-deployed template version and destroy resources associated with the drift.Resources: Ensure all Kubernetes resources (deployments, PVCs, secrets, services, etc.) are migrated to their versioned counterparts.
Count/For-Each: Pay special attention to resources using
countorfor_eachmeta-arguments when importing.
Troubleshooting
If you encounter issues during migration:
Verify both CLI and server versions match and support the
--no-buildflag.Ensure all non-versioned resources were removed from state before importing.
Confirm resource IDs and names match exactly when importing.
Check that all references to resources in the template have been updated to use versioned names.
If a PVC (or other resource) was destroyed after clicking "Update and Restart" on 2.31.1+, this is caused by the stop build running
terraform destroyagainst the drift between the pushed state and the previously-deployed template version. See the background section and use Option A or Option B in Step 13.