New to the Runpod Terraform provider? You don’t need this guide. Start with Manage Runpod infrastructure with Terraform to install 2.0.0 and deploy your first Pod.
What changed
The 1.x provider was assembled from more than one backend, so its attribute names came in several styles. Some attributes were even accepted by Terraform but ignored by the service. The 2.0.0 provider has one source of truth. Every attribute maps to a documented v2 field. Names follow the v2 schema:image, not image_name, and disk, not container_disk_in_gb. Anything the API never supported is gone rather than silently accepted.
Throughout this guide, “1.x” means 1.0.8, the last release that worked end to end and the baseline that 2.0.0 preserves.
Some things 1.x did have no v2 field yet. 2.0.0 keeps these and marks them deprecated rather than dropping them. The machine surface and the account read run on GraphQL. The three 1.x billing data sources read the v2 billing routes in their 1.x record shape. A few attributes on Pods, templates, and Serverless endpoints read from GraphQL or REST v2, or are kept only so a 1.x configuration still plans. Terraform warns about each one on every plan. The mapping tables below flag them row by row, and Deprecated shims lists them all.
2.0.0 makes no REST v1 calls, so the REST v1 retirement on November 15, 2026 doesn’t affect it.
Requirements
- Terraform 1.6 or later.
- An existing configuration and state built with the 1.x provider.
- A Runpod API key, set through the
api_keyargument or theRUNPOD_API_KEYenvironment variable. - The resource ids from your current state, recorded before you upgrade. Step 2 below covers this.
Provider block and authentication
Pin the new major version and raise the Terraform floor. The 1.x block:api_key, falls back to the RUNPOD_API_KEY environment variable, and errors at configure time if neither is set.
The base_url argument now points at a different host. In 1.0.8, base_url and the RUNPOD_BASE_URL environment variable both set the REST v1 host (https://rest.runpod.io/v1). In 2.0.0, base_url sets the REST v2 host that everything except the GraphQL shims calls. It falls back to RUNPOD_API_BASE_URL, then the production API. 2.0.0 doesn’t read RUNPOD_BASE_URL.
One more provider argument applies only to the deprecated shims. Everything on REST v2 ignores it:
graphql_url: The GraphQL endpoint for therunpod_machineresource, therunpod_machineandrunpod_machinesdata sources, therunpod_userdata source, and the deprecated attributes onrunpod_pod,runpod_template(resource and data source), andrunpod_serverless_endpointthat REST v2 doesn’t report. Falls back toRUNPOD_GRAPHQL_URL, thenhttps://api.runpod.io/graphql.
rest_v1_url argument. It’s still accepted so a configuration that sets it validates, but it’s deprecated and ignored, and RUNPOD_REST_V1_URL isn’t read. Remove it. For the full list of calls that still leave REST v2, see Which Runpod APIs the provider uses.
Upgrade process
1
Pin the new version and initialize
Set the version in your provider block and run
terraform init -upgrade:~> 2.0 installs 2.0.0 and every later 2.x release.2
Record your resource ids
Before editing anything, record the identifiers you will need to re-import. Run
terraform state list and terraform show -json against the old state, or reuse the id outputs you already have. Do this while the 1.x provider is still in the lock file.3
Rewrite the configuration resource by resource
Apply the mapping tables below, then:
- Rename the three resource types that changed name:
runpod_endpoint,runpod_container_registry_auth, andrunpod_ecr_delegation. - Rename the two data sources that changed with them:
runpod_container_registry_authsandrunpod_ecr_delegations. - Delete the resources that no longer exist.
4
Run the state actions
2.0.0 ships no state upgraders, so a 1.x state entry whose attribute set changed cannot be decoded. Follow the state migration section to remove and re-import each affected resource. Those commands change state only. For a Pod, though, the first apply after them can replace it. Read Before importing a Pod you want to keep first.
5
Plan and review
Run
terraform plan. Templates, network volumes, endpoints, machines, and account keys should show no changes when the rewritten configuration matches what the API reports. Imported Pods, registry credentials, and ECR delegations behave as the state migration section describes. Read every planned replacement before you apply it.gpu_type_id, gpu_count, cloud, cpu_type_id, vcpu_count, template_id, the CUDA constraints, or the host-size floors forces a new Pod. Switching a Serverless endpoint between the GPU and CPU families replaces the endpoint.
Resources
runpod_pod
The resource type name and id format are unchanged. The attribute set changed substantially, and the two mount kinds moved into blocks. These attributes were renamed, reshaped, or moved:
The container command now lives entirely in
args as a single string. docker_entrypoint and docker_start_cmd still exist in 2.0.0 as read-only attributes, read back from the v2 Pod’s entrypoint and cmd. 1.0.8 accepted values for them but never sent those values. Remove them from your configuration and put the command in args.
machine_id and volume_encrypted also still exist, as deprecated read-only values from the GraphQL Pod. machine_id reports the machine the Pod landed on. You can’t set it, because the v2 create has no machine pinning. volume_encrypted reads false for an unencrypted volume, not null as in 1.x, so a check such as volume_encrypted == null needs updating.
These attributes keep their names and behavior: gpu_type_id, gpu_count, template_id, start_ssh, start_jupyter, memory_in_gb, id, status, and created_at. locked and cluster_ip also keep their names and are now populated from the v2 read, where 1.x declared them but never filled them.
2.0.0 adds these attributes:
cpu_type_id: creates a CPU-only Pod. Exactly one ofgpu_type_idorcpu_type_idmust be set.desired_state:RUNNINGorEXITED, reconciled on every apply.data_center_ids: a placement preference. The scheduler’s pick lands in the read-onlydata_center_id.registry: the registry credential id for a private image.global_networking: a private cross-datacenter IP.min_cuda_versionandallowed_cuda_versions: a CUDA floor, or an exact set. These are mutually exclusive.min_ram_per_gpuandmin_vcpu_count_per_gpu: host-size placement filters, applied per requested GPU.global_volumes: a list of{ volume_id, path }objects that mounts global volumes wherever the Pod is placed.
network_volumes block. The two mount kinds are mutually exclusive, and the volume must be in the same data center as the Pod:
runpod_endpoint
The 2.0.0 equivalent isrunpod_serverless_endpoint: the resource type was renamed. The largest change is GPU selection. 1.x named individual GPU types; 2.0.0 names Serverless GPU pools and subtracts exceptions from them.
These attributes were renamed, reshaped, or moved:
These attributes keep their names and behavior:
name, env, template_id (which must be a serverless template), gpu_count, workers_min, workers_max, idle_timeout, allowed_cuda_versions, min_cuda_version, data_center_ids, id, and created_at.
gpu_type_priority and cpu_flavor_priority are still in the 2.0.0 schema as deprecated attributes, but they’re never applied. No API has a setting for the order in which the scheduler tries GPU types or CPU flavors, so a configured value stays in state, is never sent, and triggers a plan warning. Remove them from your configuration.
template_version, users, version, and workers are also still available as deprecated read-only values:
template_versionis always null. No API reports it. Use therunpod_endpoint_releasesdata source for releases.usersis the owning account as a one-element list, read from GraphQL. It was never an access list.versionandworkerscome from the REST v2 workers listing. Inworkers,pod_idis the worker’sid,uptime_mshas whole-second precision, andlast_busy_msis always null. The supported view is therunpod_endpoint_workersdata source.
cloud_type is gone. It existed only in 1.0.9, which never ran, and the v2 Serverless create takes no cloud tier.
2.0.0 adds these attributes:
endpoint_type: required, eitherQUEUEorLOAD_BALANCER. Changing it replaces the endpoint.ports: the worker HTTP ports aLOAD_BALANCERendpoint routes to.excluded_types: GPU type ids subtracted from the selected pools.registry: the registry credential id for a private worker image.global_volumes: global volumes mounted on every worker of a GPU endpoint.
runpod_network_volume
The resource type name and id format are unchanged. Two attributes were renamed:name, size (10 to 4096 GB, which can grow but never shrink), and id keep their names and behavior.
Before, in 1.x:
runpod_template
The resource type name and id format are unchanged. The container settings come from the v2 template object,docker_entrypoint included, and the single persistent mount moved into the persistent_volume block.
A template that sets both docker_start_cmd and docker_entrypoint needs one extra edit. The API stores the entrypoint inside args, so args and docker_entrypoint can’t both be set. Write args in its JSON form instead, for example args = jsonencode({ entrypoint = ["/bin/bash", "-c"], cmd = ["start.sh"] }).
These attributes were renamed, reshaped, or moved:
name, category (CPU, NVIDIA, or AMD), env, ports, and id keep their names and behavior.
Before, in 1.x:
runpod_container_registry_auth
The 2.0.0 equivalent isrunpod_registry: the resource type was renamed. Every attribute (name, username, password, and id) kept its name. The API has no update operation, so any change replaces the credential, and 2.0.0 cannot import one.
Before, in 1.x:
registry:
runpod_ecr_delegation
The 2.0.0 equivalent isrunpod_registry_delegation: the resource type was renamed. Every attribute kept its name, and resource is now required. The API has no update operation, so any change replaces the delegation, and 2.0.0 cannot import one.
Before, in 1.x:
runpod_pod_action
The resource type name is unchanged, but the semantics are now explicit: the resource is imperative. It reconciles nothing and refreshes to nothing, and destroying it only forgets it, leaving the Pod as it is. The action runs on create and again wheneveraction or triggers change.
pod_idkeeps its behavior. Pointing it at a different Pod replaces the resource and runs the action on the new Pod.actionkeeps the namesstart,stop,restart, andterminate. 1.x also documentedresumeandreset; usestartandrestartinstead.statusis read-only and reports the status at the moment the action was accepted, not a live value. A refresh does not update it, andterminatereturns no body, so it leaves this null. Read live state from therunpod_poddata source.triggersis new: a map of arbitrary values that re-runs the action when any of them changes.idis new and read-only: the Pod this resource acts on, one action runner per Pod.
desired_state on the Pod instead:
runpod_machine
Therunpod_machine resource remains available on the GraphQL API as a deprecated shim. The resource type name, id format, and every attribute name are unchanged. No configuration change is needed beyond the state action. Only name and bid_price_per_gpu are settable, exactly as in 1.x; everything else reports what the API assigned. Terraform prints a deprecation warning on every plan that touches it.
Removed resources
Two 1.x resources have no 2.0.0 equivalent:runpod_endpoint_job: jobs are runtime objects served by the Serverless runtime API, not infrastructure Terraform should own. Submit jobs from your application or a caller against the runtime API.runpod_endpoint_worker: individual workers are not addressable. Use therunpod_endpoint_workersandrunpod_endpoint_worker_logsdata sources to observe workers, and the endpoint’sworkers_min,workers_max,scaling_type, andscaling_valueto control them.
Data sources
Data sources hold no managed state, so none of them needs a state action. Rename the two that changed name and delete the removed ones; the rest re-read on the next plan.runpod_pod
Therunpod_pod data source keeps its name and required id. It now reports far more than 1.x did, including live cost, runtime utilization, SSH details, mounts, and cluster membership.
These attributes were renamed, reshaped, or moved:
id, name, gpu_count, gpu_type_id, status, and created_at keep their names. The desired_status, machine_id, and machine_type attributes are gone; the 1.x data source could never populate them, so no configuration depended on them.
2.0.0 adds cloud, data_center_id, started_at, locked, registry, actions, cpu_type_id, cpu_vcpu_count, gpu_vcpu_count, cuda_version, the global_networking fields, mounts.network, runtime (CPU, memory, and per-GPU utilization), ssh.direct and ssh.proxy, and cluster.
runpod_machine and runpod_machines
Both data sources remain available on the GraphQL API as deprecated shims. Each keeps its name, its required inputs, and every attribute name. As in 1.x,data_center_id is always null on runpod_machine, because the underlying query does not select it; use runpod_machines when you need the data center.
runpod_gpu_types
Therunpod_gpu_types data source is now read from the v2 catalog API. The list attribute is still gpu_types, and its nested fields were renamed to the catalog’s names:
gpu_types[].id keeps its name. The cuda_cores and manufacturer fields are gone, because the v2 catalog does not carry them. 2.0.0 adds gpu_types[].community (availability on Community Cloud) and gpu_types[].pool (the Serverless pool id for runpod_serverless_endpoint.gpu_pools). A new single-item companion, runpod_gpu_type, looks up one entry by id.
runpod_data_centers
Therunpod_data_centers data source is now read from the v2 catalog API. The list attribute is still data_centers:
data_centers[].id, data_centers[].name, and data_centers[].global_network keep their names. 2.0.0 adds data_centers[].compliance (compliance certifications held) and data_centers[].network_volume_types (storage tiers offered). A new single-item companion, runpod_data_center, looks up one entry by id.
runpod_template
Therunpod_template data source keeps its name and required id, and mirrors the resource’s renames. image_name becomes image, container_disk_in_gb becomes disk, is_public becomes public, is_serverless becomes serverless, and container_registry_auth_id becomes registry. docker_start_cmd becomes args, reshaped from a list to a single string. volume_in_gb and volume_mount_path move into mounts.persistent.size and mounts.persistent.path. docker_entrypoint keeps its name and is read from the v2 template. readme, earned, is_runpod, and runtime_in_min are deprecated read-only values from GraphQL. 2.0.0 adds allowed_cuda_versions, start_ssh, and start_jupyter.
runpod_container_registry_auths
The 2.0.0 equivalent isrunpod_registries: the data source was renamed, and its list attribute container_registry_auths became registries. The id and name nested fields keep their names. registries[].username is deprecated and always null. REST v2 treats the username as write-only, and no other supported API reports it. The password, created_at, and updated_at fields are gone. They existed only in 1.0.9, which never ran, and the v2 list doesn’t return them.
runpod_ecr_delegations
The 2.0.0 equivalent isrunpod_registry_delegations: the data source was renamed along with the resource, and its list attribute ecr_delegations became delegations. The nested id, name, aws_user, aws_region, repository, tag, docker_registry_uri, and created_at fields keep their names. The resource field is gone, because the API does not echo the delegated ARN; read it from your own configuration. 2.0.0 adds delegations[].delegator_user_id, the Runpod user that created the delegation.
runpod_user
Therunpod_user data source is kept as a deprecated shim. It now reads the GraphQL myself query, because the REST v1 /user route is gone. Every attribute keeps its name, but only id and email have a source. The others read null.
runpod_endpoint_workers
Therunpod_endpoint_workers data source keeps its name and required endpoint_id, with richer per-worker data and a status summary:
workers[].id and workers[].status keep their names. The synthetic top-level id, along with workers[].pod_id and workers[].last_busy_ms, are gone, because the v2 worker list does not carry them. 2.0.0 adds per-worker data_center_id, gpu_type_id, gpu_count, image, and is_stale, plus a summary of counts by status and the endpoint’s current endpoint_version.
runpod_endpoint_worker_logs
Therunpod_endpoint_worker_logs data source keeps its name and required endpoint_id and worker_id. The API serves logs as a live event stream. A read collects what the stream delivers and ends when the API closes it or the provider’s 60-second timeout elapses. The logs attribute (one string) became lines (a list of line strings); use join to get the old shape. The synthetic id is gone. 2.0.0 adds entries (per-event line, timestamp, and source) and the tail, since, last_event_id, and source arguments to bound the read.
Billing data sources
The three 1.x billing data sources still exist in 2.0.0. They’re deprecated and keep their 1.x names, arguments, and record fields, but they now read the v2 billing routes and map each record back into the 1.x shape, so some records differ from 1.x. See Deprecated shims. New configuration should use the v2-native equivalents below, which report cost components in USD. The record shape changed, so moving to them is a rewrite rather than a rename. In every case thebilling_records list became records, billing_records[].amount became records[].amounts.total_amount, and the single time field became a start_time and end_time pair on each record. Each data source also adds a last_n argument and a metadata block. The last_n argument returns the last N buckets and is mutually exclusive with an explicit window. The metadata block describes the resolved window and its totals.
Removed data sources
runpod_endpoint_jobs and runpod_endpoint_job_logs have no 2.0.0 equivalent, because jobs and their logs live on the Serverless runtime API. For worker-level output, use runpod_endpoint_worker_logs, and for Pod output, use the new runpod_pod_logs.
New in 2.0.0
2.0.0 adds capabilities that 1.x had no surface for. Clusters are multi-node groups of identical Pods on one private overlay network, with SLURM, Ray, training, and application topologies. Read them withrunpod_cluster, runpod_clusters, and runpod_cluster_pods.
global_volumes wherever it’s placed, and a GPU Serverless endpoint mounts one on every worker through its own global_volumes. Read one with the runpod_global_volume data source.
runpod_cpu_type and runpod_cpu_types catalog data sources.
- Catalog templates: the publicly shared template catalog, read through
runpod_catalog_templates. - Serverless builds and releases:
runpod_endpoint_buildsandrunpod_endpoint_buildreport container builds from a Git repository, andrunpod_endpoint_releasesreports configuration releases with a field-level diff against the previous one. - Billing on the v2 routes:
runpod_billing(account-wide),runpod_pod_billing,runpod_serverless_billing,runpod_endpoint_billing,runpod_network_volume_billing, andrunpod_cluster_billing, all with the same window arguments and per-component totals. - List data sources:
runpod_pods,runpod_templates,runpod_serverless_endpoints, andrunpod_network_volumesread every resource of that kind on the account. - Pod logs:
runpod_pod_logs, the Pod-level counterpart to worker logs, bounded bytail,since, orlast_event_id. - Single-item catalog lookups:
runpod_gpu_type,runpod_cpu_type, andrunpod_data_center, so you no longer filter a whole list to read one entry. - FlashBoot tiers: cold-start acceleration is now a three-value setting (
OFF,FLASHBOOT, orPRIORITY_FLASHBOOT) rather than a boolean. - Declarative Pod run state:
runpod_pod.desired_state(RUNNINGorEXITED) makes a Pod stopped outside Terraform show up as a plan diff and get started again on the next apply.
Removed in 2.0.0
Some 1.x attributes are gone in 2.0.0. Most of them were declared in the 1.x schema but never sent to the service, so removing them changes no behavior.- Spot Pods (
type = "SPOT",interruptible, andbid_per_gpu) were retired by the platform. The APIs stopped accepting bids, so migrate these configurations to on-demand Pods. stop_after,terminate_after,port, andvolume_keyonrunpod_podwere schema-only in 1.x and have no v2 counterpart. Usedesired_stateorrunpod_pod_actionfor a scheduled stop or terminate, declare ports as a list inports, and note that the v2 API has no volume encryption key.- Machine pinning on Pods never worked in 1.x, and the v2 Pod create has no machine parameter. Constrain placement with
data_center_idsand the per-GPU RAM and vCPU floors instead. cloud_typeon endpoints, andpassword,created_at, andupdated_aton the registry list existed only in 1.0.9, which never ran. They were never in 1.0.8.
Deprecated shims
A shim is a small piece of code inside the provider that keeps a 1.x feature working when REST v2 has no equivalent yet. For a runnable example, see Legacy shims on the Terraform Registry. Everything a 1.0.8 configuration used that REST v2 has no field for still plans in 2.0.0, marked deprecated. Where only GraphQL has the value, the provider reads or writes it there throughgraphql_url. Where no API has the value anymore, the attribute is kept so the configuration still plans, and it reads null or isn’t applied. Terraform prints a deprecation warning on every plan that touches a shim, and each shim is removed in a later release once REST v2 covers it.
2.0.0 makes no REST v1 calls, so the REST v1 retirement on November 15, 2026 doesn’t affect any shim.
The
runpod_machine resource, the runpod_machine and runpod_machines data sources, and the runpod_user data source also run on GraphQL. Only id and email are populated on runpod_user.
For the three billing data sources, end_time must be an RFC 3339 timestamp, a date, or a datetime without a zone (read as UTC). The window is the 30 days before end_time, or before the current time when it’s unset.
When GraphQL fails, an unconfigured shim attribute reads null with a warning and the apply goes through. A shim attribute your configuration sets fails the apply. The billing data sources always fail on a bad read.
compute_type on runpod_serverless_endpoint is deprecated for a different reason. It restates what gpu_pools or cpu already says, and it goes away once 1.x configurations have moved, not when an API changes.
Don’t build new configuration on any shim.
State migration
2.0.0 ships no state upgraders. Every resource schema is at version 0 in both majors. Terraform does not rewrite a 1.x state entry into the 2.0.0 shape. Wherever the attribute set changed, the old entry cannot be decoded and must be removed and re-imported.terraform state rm and terraform import change state only: running them stops no Pod and deletes no volume. Do the state work after rewriting the configuration and before the first terraform apply.
Before importing a Pod you want to keep
There are two safe ways through:- Leave a running 1.x Pod on 1.x. Do not remove and import it. Keep it in a root module pinned to
version = "<= 1.0.8". Don’t use~> 1.0, which selects 1.0.9. That release can’t serve its own schema or configure most of its resources. Let that provider manage it until the Pod is due to be recreated anyway. Then create its successor under 2.0.0. - Adopt it with
ignore_changes. If the Pod has to move to 2.0.0 now, add alifecycleblock before the first apply. Name every one of the eight attributes the configuration sets. Listing all eight is harmless if you are unsure which the Pod was created with.
ignore_changes is that Terraform never enforces those values on this Pod. State keeps them empty, so changing one later plans nothing at all. To apply a new value, take the attribute out of ignore_changes and accept the replacement. On a GPU Pod, also set cloud and gpu_count to the values the Pod already has. Leaving either out makes any other change plan as unknown, and both force replacement. Also, an import reads the Pod’s whole environment into state, so the first apply that sets env removes every key the configuration leaves out, including keys that came from a template. The plan lists those removals.
Either way, read the first plan before applying it. It must not replace a Pod you mean to keep, and the # forces replacement marker names the attribute responsible.
Re-import with the same type name
The type name does not change, and the id is the one you already have. The attribute set still changed, though, so remove and import each resource. This applies torunpod_pod, runpod_template, runpod_network_volume, and runpod_machine. For runpod_machine, the attribute names are unchanged, but re-import anyway because the resource is a different implementation.
Re-import with a renamed type
The old address cannot be read once 2.0.0 is in the lock file, because the type no longer exists.terraform state mv does not help either, because the type is part of the address and the schemas differ. Remove the old address, then import the new one against the same id. This applies to runpod_endpoint, which becomes runpod_serverless_endpoint.
Recreate a renamed type that cannot be imported
The credential and delegation types were renamed, and neither can be imported, because the v2 API does not return the values needed to reconstruct them. Remove the old address and let the new resource be created; it gets a new id, so update everyregistry reference on Pods and endpoints. The 1.x resource stays on the account until you delete it in the console. This applies to runpod_container_registry_auth, which becomes runpod_registry, and runpod_ecr_delegation, which becomes runpod_registry_delegation.
Remove and re-declare
runpod_pod_action holds no Pod state and cannot be imported, so remove the old address and re-declare it.
Remove with nothing to import
Therunpod_endpoint_job and runpod_endpoint_worker resource types are gone, so remove them from state:
account.
Final check
After the state work, runterraform plan. Expect:
- No changes for templates, network volumes, endpoints, and machines.
- For each imported Pod, no replacement, and at most an in-place update for the
envanddesired_statethat an import leaves empty. - A create for each registry credential and delegation.
- A refresh on the log, worker, and billing data sources.
# forces replacement marker. Fix the configuration rather than applying it.
Next steps
- Review the provider reference on the Terraform Registry for every 2.0.0 argument and attribute.
- Read Manage Runpod infrastructure with Terraform for authentication options and the APIs the provider calls.