General ·
Manage apps and DNS with Terraform
Install the headpat Terraform provider, describe an app with a custom hostname and its DNS records in HCL, import existing resources, and handle the payment step Terraform cannot do.
If you already keep your infrastructure in Terraform, your Headpat apps, custom hostnames and DNS records can live there too. The headpat provider talks to the same API as the panel, authenticated with an API key. This guide installs it, creates an app with a hostname and a DNS record, and explains the one step Terraform cannot do for you: paying.
What you need
- Terraform 1.5 or newer (OpenTofu works the same way).
- An API key with the scopes
apps:read,apps:write,dns:readanddns:write; see Automate the panel with API keys and the CLI for how to create one.
1. Install the provider
The provider is not on the public Terraform registry yet, so Terraform is told where to find it. Download the zip for your platform from the latest Terraform provider release (the releases are named Terraform provider x.y.z) and unpack the binary into a folder of your choice, for example ~/.terraform.d/headpat.
Then point Terraform at that folder with a dev override in your CLI configuration file, ~/.terraformrc on Linux and macOS, %APPDATA%\terraform.rc on Windows:
provider_installation {
dev_overrides {
"headpatcloud/headpat" = "/home/you/.terraform.d/headpat"
}
direct {}
}
With a dev override Terraform skips terraform init for this provider and prints a warning at every run that it is using a local build. That is expected.
2. Configure it
terraform {
required_providers {
headpat = {
source = "headpatcloud/headpat"
}
}
}
provider "headpat" {}
The provider reads the key from the environment variable HEADPAT_API_KEY. You can also set api_key in the provider block, for example from a Terraform variable; never commit the key itself.
3. Describe an app, a hostname and a DNS record
data "headpat_app_catalog" "all" {}
resource "headpat_dns_zone" "example" {
zone_name = "example.com"
}
resource "headpat_app" "api" {
template = "container-app"
tier = "standard"
name = "api"
image = "ghcr.io/acme/api:1.4.0"
port = 3000
env = {
NODE_ENV = "production"
}
volume_path = "/data"
}
resource "headpat_app_domain" "api" {
app_id = headpat_app.api.id
hostname = "api.example.com"
}
resource "headpat_dns_record" "api" {
zone_id = headpat_dns_zone.example.id
name = "api.example.com"
type = "CNAME"
values = [headpat_app_domain.api.dns_target]
}
resource "headpat_dns_record" "api_ownership" {
zone_id = headpat_dns_zone.example.id
name = "${headpat_app_domain.api.ownership_txt_label}.api.example.com"
type = "TXT"
values = ["\"${headpat_app_domain.api.ownership_txt_value}\""]
}
output "hostname" {
value = headpat_app.api.hostname
}
template and tier are the slugs from the catalog; data.headpat_app_catalog.all.templates lists them with prices. Changing image, port, env or volume_path updates the running app in place; changing template, tier or name replaces it. For the other templates, WordPress or PostgreSQL say, leave out the Container App attributes.
headpat_app_domain exposes everything the panel shows for a custom hostname: the CNAME target, the A record alternative for apex names, and the ownership TXT record. Feeding those into headpat_dns_record as above means one terraform apply creates the app, attaches the hostname and publishes the records that validate it.
export HEADPAT_API_KEY=...
terraform plan
terraform apply
4. Paying for new resources
Terraform cannot complete a Stripe checkout. When your account pays per resource, creating a headpat_app returns a draft and a checkout_url, and the apply finishes with a warning that names it. Open the URL, pay, and the app provisions; the next terraform plan or terraform refresh shows it as running. Accounts that are billed otherwise skip this step and the provider waits for the app to come up (create_timeout_minutes, ten by default).
Importing what already exists
terraform import headpat_app.api <app-id>
terraform import headpat_app_domain.api <app-id>/<domain-id>
terraform import headpat_dns_zone.example <zone-id>
terraform import headpat_dns_record.api <zone-id>/api.example.com/CNAME
The ids are in the panel URLs and in headpat apps list --json and headpat dns zones --json from the CLI.
Destroying
terraform destroy on a headpat_app cancels the subscription at the end of its billing period by default, so the app keeps running until then. Set delete_immediately = true on the resource if it should disappear at once; data is gone with it.
Reference
The resources and data sources, with every attribute, are documented at terraform/docs in the release repository, next to a complete example. Available today: headpat_app, headpat_app_domain, headpat_dns_zone, headpat_dns_record, and the data sources headpat_app_catalog, headpat_app and headpat_dns_zones.