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-build flag

  • coder/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.x2.33.x2.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 conditional count on 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:

    1. 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).

    2. 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), and

  • The 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 destroydeletes 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.102.28.72.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>.tfstate

Step 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>.tfstate

Review 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:

  1. Change resource names to include the version suffix:

    • Before: kubernetes_deployment

    • After: kubernetes_deployment_v1

  2. Apply this change to all non-versioned Kubernetes resources (deployments, PVCs, secrets, services, etc.).

  3. 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.main

Repeat 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>.tfstate

Ensure 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-home

Important 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.main

    • The [0] index indicates you're importing the first instance of the resource created by count.

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>.tfstate

The --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-build until 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 count or for_each meta-arguments when importing.

Troubleshooting

If you encounter issues during migration:

  • Verify both CLI and server versions match and support the --no-build flag.

  • 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 destroy against 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.

Additional Resources