Reference / Terraform
Terraform provider
Manage instances, networks, load balancers, Kubernetes clusters, storage, SSH keys, databases and their snapshots, and webhooks as code with the pantechdynamics Terraform provider.
The pantechdynamics provider lets Terraform (or OpenTofu) create and change your Pantech resources. It talks only to the public API, with an API key, so it can do what a key can do.
The provider is published on the Terraform Registry:
- Registry page: registry.terraform.io/providers/Pantech-Dynamics/pantechdynamics
- Source address:
Pantech-Dynamics/pantechdynamics - Source code: github.com/Pantech-Dynamics/terraform-provider-pantechdynamics
New to Terraform? Follow create every service, then delete it for a step-by-step walk through each service.
Using an AI app (Claude, ChatGPT, Cursor…) as well? Read Terraform or an AI app? so the two don't change the same resources.
Install
Declare the provider, then run terraform init:
terraform {
required_version = ">= 1.11"
required_providers {
pantechdynamics = {
source = "Pantech-Dynamics/pantechdynamics"
version = "~> 0.1"
}
}
}Terraform 1.11 or later is needed for databases, whose password is a write-only attribute. Everything else works on older versions. terraform init downloads the provider from the registry and checks its signature.
Configure
provider "pantechdynamics" {
base_url = "https://api.pantechdynamics.com/public/v1"
api_key = var.pantechdynamics_api_key
}
variable "pantechdynamics_api_key" {
type = string
sensitive = true
}Or leave both out of the file and set them in the environment, which keeps the key out of your code:
$ export PANTECHDYNAMICS_BASE_URL=https://api.pantechdynamics.com/public/v1$ export PANTECHDYNAMICS_API_KEY=PAN_…$ terraform plan| Setting | Environment variable | |
|---|---|---|
base_url | PANTECHDYNAMICS_BASE_URL | Required. The API's base URL, including /public/v1 |
api_key | PANTECHDYNAMICS_API_KEY | Required. A write key to apply; a read key is enough to plan |
request_timeout | PANTECHDYNAMICS_REQUEST_TIMEOUT | How long one API call may take, like "90s". Defaults to 60 seconds |
request_timeout bounds a single request. How long an apply waits for a resource to be ready is that resource's own timeouts block.
Use a key made for Terraform alone, with an expiry, so you can revoke it without touching anything else.
What it manages
| Resource | What it is |
|---|---|
pantechdynamics_instance | A virtual machine, standard or in a VPC. Creating one places a paid order. encrypted = true creates an encrypted VM from the image you name; deletion_protection is read-only |
pantechdynamics_ssh_key | A public key to install on new instances |
pantechdynamics_security_group | A reusable firewall for standard instances |
pantechdynamics_network | A VPC |
pantechdynamics_subnet | A subnet of a VPC |
pantechdynamics_firewall_rule | A rule on a subnet's network ACL |
pantechdynamics_public_ip | A public address for a VPC: static NAT, port forwarding or load balancing. A static NAT address's instance_id changes in place, moving the address between VMs, or leave it out to hold the address detached |
pantechdynamics_port_forwarding_rule | A port forward on a public IP |
pantechdynamics_load_balancer | A load balancer: one TCP port spread across VMs, vpc_native in a VPC subnet or appliance in a standard zone |
pantechdynamics_kubernetes_cluster | A managed Kubernetes cluster: a fixed worker count or an autoscaling range, its pod network (cni_plugin, pod_cidr, service_cidr), endpoint_access (private unless you set it; a public or dual cluster reports its public_ip and public_port), an API server allow-list for a public or dual cluster, and its kubeconfig as a sensitive attribute. node_plan, the network and the endpoint change in place |
pantechdynamics_kubernetes_node_pool | A group of workers with its own plan, labels and taints, a fixed size or an autoscaling range; observed_workers is how many have joined (and are billed) |
pantechdynamics_kubernetes_addon | A managed add-on turned on or off: metrics-server, csi-driver (node-local volumes) or ingress-controller; status shows what the cluster runs |
pantechdynamics_volume | A data disk, attached or not |
pantechdynamics_snapshot | A snapshot of an instance's root disk or a volume |
pantechdynamics_snapshot_schedule | Automatic snapshots of an instance or a volume |
pantechdynamics_database | A managed database (see below) |
pantechdynamics_database_snapshot | A snapshot of a database's data disk |
pantechdynamics_webhook | A webhook: a URL Pantech sends events to, with its signing secret as a sensitive attribute |
Data sources read the catalogue, so a wrong slug fails at plan time rather than at apply: pantechdynamics_plans and pantechdynamics_plan, pantechdynamics_images and pantechdynamics_image, pantechdynamics_regions and pantechdynamics_region, pantechdynamics_disk_offerings and pantechdynamics_database_engines. pantechdynamics_kubernetes_versions lists the Kubernetes versions in a zone and the zones that offer 3 control nodes. pantechdynamics_webhook_event_types lists the webhook event types. Six more look up something you already have, by id or name: pantechdynamics_instance, pantechdynamics_network, pantechdynamics_security_group, pantechdynamics_ssh_key, pantechdynamics_load_balancer and pantechdynamics_kubernetes_cluster.
Each resource's arguments and examples are in the provider's documentation on the Terraform Registry.
An example
resource "pantechdynamics_ssh_key" "admin" {
name = "admin"
public_key = file("~/.ssh/id_ed25519.pub")
}
resource "pantechdynamics_instance" "web" {
name = "web-1"
plan_slug = "starter"
image_slug = "ubuntu-24-04"
ssh_key_id = pantechdynamics_ssh_key.admin.id
}An instance or a database costs money the moment it is created, and terraform apply creates it without asking twice. Read the plan.
Databases
A managed database is configured like this:
resource "pantechdynamics_database" "orders" {
name = "orders-db"
engine = "postgresql"
version = "18"
plan_slug = "starter"
zone_id = "af-abj-2"
subnet_id = pantechdynamics_subnet.data.id
admin_username = "app_admin"
access_rules = ["10.0.1.0/24"]
storage_gb = 100
# Write-only: sent to the API, never saved in the plan or the state.
password_wo = var.orders_db_password
password_wo_version = 1
}
output "orders_db_host" {
value = pantechdynamics_database.orders.hostname
}- The password stays out of state.
password_wois a write-only attribute (Terraform 1.11+): Terraform passes it to the API and never stores it. Because Terraform cannot see it afterwards, it cannot tell when you change it, so raisepassword_wo_versionto set a new password. - Pass it from somewhere safe, such as a sensitive variable filled from your secret store, or an ephemeral resource that generates one.
password_wois required. A password the API generates is returned only once, and Terraform could not keep it out of state, so with Terraform you always choose it.access_rulesis the whole allow-list, as CIDRs. Leave it out for the zone's default, or set[]for none. Removing a CIDR also ends connections already open from it.desired_stateis"running"(the default) or"stopped". A password change needs the database running, so it is started for the change and stopped again afterwards.- Changing the name, engine, version, plan, zone, subnet or admin login replaces the database, which deletes its data.
storage_gbis the data disk's size: the plan'sdisk_gbif you leave it out, otherwise up to 2,000 GB in 10 GB steps (the zone's limits, from the engines data source'sstorage). Raise it to grow the disk in place, online; a stopped database is started for the resize and stopped again. A smaller value is an error at plan time: the disk never shrinks, and the database is never replaced to make it smaller.data_volume_size_gbis the size in place and billed.security_group_idsattaches up to 5 security groups as sources of address ranges;effective_access_rulesandignored_security_group_rulesare read back. See access rules.hostname,portandprivate_ipare read back once the database is running.- A
pantechdynamics_database_enginesdata source lists the engines and versions you can create, likeGET /database-engines. Each engine'sstoragegives every zone's data disk limits forstorage_gb(min_gb,min_is_plan_disk,max_gb,step_gb) and its price (price_per_gb_month_minorandcurrency, null when not priced).
Snapshots of a database
resource "pantechdynamics_database_snapshot" "before_migration" {
database_id = pantechdynamics_database.orders.id
name = "before-migration"
}A database snapshot is crash-consistent; set the database's desired_state = "stopped" first for a quiesced copy. Changing database_id or name replaces the snapshot. A snapshot outlives its database and is billed until it is destroyed, so remove it from your configuration when you no longer need it.
Reaching a database from a standard VPS
In af-abj-1, set private_network = true on a standard instance to give it an interface on the private database network, and allow its private_network_ip on the database:
resource "pantechdynamics_instance" "web" {
name = "web-1"
plan_slug = "starter"
image_slug = "ubuntu-24-04"
ssh_key_id = pantechdynamics_ssh_key.admin.id
security_group_id = pantechdynamics_security_group.web.id
private_network = true
}
resource "pantechdynamics_database" "orders" {
name = "orders-db"
engine = "postgresql"
version = "18"
plan_slug = "starter"
zone_id = "af-abj-1"
access_rules = ["${pantechdynamics_instance.web.private_network_ip}/32"]
password_wo = var.orders_db_password
password_wo_version = 1
}- The instance's security group must admit nothing from
10.250.0.0/20(the zone'sprivate_network_cidron thepantechdynamics_regionandpantechdynamics_regionsdata sources' placements), ICMP included, or the attach fails withSECURITY_GROUP_ALLOWS_PRIVATE_NETWORK. Narrow it in the same configuration; see connect a standard VPS. - Changing
private_networkattaches or detaches the interface in place, without a restart. Leaving it out leaves the interface as it is. It is refused on an instance in a subnet. - Inside the VPS the interface stays down until you configure it, with DHCP and without its routes or DNS.
Import
Resources already made in the console or with the API can be brought under Terraform with terraform import and their id, for example:
$ terraform import pantechdynamics_instance.web vm_06gk3x9tq7m2v8r4y1c5d0zp8w