Deploy with Terraform
Declare projects, networks, applications and secrets with the Hakopod Terraform provider, against the same API the CLI uses.
The Hakopod Terraform provider declares the same objects you create in the dashboard: projects and their environments, virtual networks, applications and secrets. It sends the same application TOML the CLI sends, to the same deployment API, so the server remains the only validator. Terraform adds the dependency graph, the plan and the state file; it does not add a second configuration format to keep in sync.
The provider is published at registry.terraform.io/providers/hakopod/hakopod and developed at github.com/hakopod/terraform-provider-hakopod. Version 0.1.0 is pre-1.0: resource schemas can still change between minor versions, so pin the version and read the release notes before upgrading.
Install the provider#
Declare the provider in your root module and pin it:
terraform {
required_providers {
hakopod = {
source = "hakopod/hakopod"
version = "~> 0.1"
}
}
}
provider "hakopod" {}
Then download it:
terraform init
Configure credentials#
The provider reads the same two environment variables as the CLI:
| Variable | Value |
|---|---|
HAKOPOD_API_URL |
Your API origin, such as https://deploy.example.com, without a trailing slash. |
HAKOPOD_API_KEY |
A named, expiring API key scoped to the projects Terraform manages. |
export HAKOPOD_API_URL="https://deploy.example.com"
export HAKOPOD_API_KEY="..."
The equivalent url and api_key provider attributes exist for setups that read credentials from a Terraform variable or another provider. Prefer the environment variables when you can: an api_key passed as an attribute is written to state.
A worked example#
This module creates a project, a virtual network with one segment, a generated secret, and two applications on the network. An application's name and services come from its configuration, not from a Terraform attribute: config takes the same application TOML the CLI deploys, and spec takes the equivalent JSON. The web application reads the API's private address from the API application's computed service_hostnames map, which also gives Terraform the ordering between them without a depends_on.
resource "hakopod_project" "shop" {
name = "shop"
environments = ["production"]
}
resource "hakopod_virtual_network" "internal" {
project = hakopod_project.shop.name
environment = hakopod_project.shop.environments[0]
spec = jsonencode({
schema_version = 1
name = "internal"
segments = {
backend = { applications = ["web", "api"] }
}
})
}
# Secrets belong to one application and are referenced by name from its
# configuration as { ref = "database-password" }.
resource "hakopod_secret" "database_password" {
project = hakopod_project.shop.name
environment = hakopod_project.shop.environments[0]
application = "api"
name = "database-password"
generate = true
}
# The network name is passed into the TOML template, so the reference orders
# this application after the network.
resource "hakopod_application" "api" {
project = hakopod_project.shop.name
environment = hakopod_project.shop.environments[0]
config = templatefile("${path.module}/api/hakopod.toml.tftpl", {
network = hakopod_virtual_network.internal.name
})
}
resource "hakopod_application" "web" {
project = hakopod_project.shop.name
environment = hakopod_project.shop.environments[0]
spec = jsonencode({
schema_version = 1
name = "web"
networks = {
backend = {
internal = true
virtual_network = hakopod_virtual_network.internal.name
segment = "backend"
}
}
services = {
web = {
image = "nginx:stable-alpine"
port = 8080
public = true
networks = ["backend"]
env = {
API_URL = "http://${hakopod_application.api.service_hostnames["api"]}:8080"
}
}
}
})
}
Run terraform plan to review the change and terraform apply to deploy it. Creating or updating a hakopod_application deploys it as one revision, exactly as a CLI deployment does, and the apply finishes when the rollout is resolved.
The provider repository carries longer examples: a static site, a six-application stack of web, api, worker, scheduler, PostgreSQL and Redis on a virtual network, a five-service mesh generated from a single map, one module per environment, and a directory tree of hakopod.toml files consumed as configuration.
Resources#
| Resource | What it manages |
|---|---|
hakopod_project |
A project and its environments. Environments can be added; removing one from the list is not supported. |
hakopod_application |
An application and all of its services, from application TOML via config or JSON via spec. Services are not separate resources. Exposes the computed service_hostnames map of private DNS names. |
hakopod_virtual_network |
A virtual network and the segment grants on it. |
hakopod_secret |
One application's secret, either a literal value or one the server generates with generate = true. A generated value never enters Terraform state; value_version changes on rotation, so a service that puts it in an environment variable redeploys with the new value. |
An application is one resource because it is one deployable unit. Splitting services into separate resources would let Terraform apply half of a release; keeping them together means each apply is a single reviewable revision.
Names are retired permanently#
A destroyed application's or project's name can never be reused. Hakopod retires the name when the object is destroyed, so the usual Terraform reflex of terraform destroy followed by terraform apply fails on the second step, and so does any change that forces replacement of a named resource.
Plan for this before it bites:
- Read every plan for
# forces replacementon ahakopod_projectorhakopod_application. Treat it as a name you are spending, not a resource you are refreshing. - Do not use throwaway workspaces that create and destroy the same names. Give ephemeral environments generated names, or keep long-lived environments on a project you do not destroy.
- Use
terraform state rmandterraform importto move an existing application between modules or state files. Destroy-and-recreate loses the name.
Secrets and virtual networks are not affected; only project and application names are retired.
CLI or Terraform?#
Both talk to the same API and both produce the same revisions, so this is a workflow choice rather than a capability one:
- CLI for the everyday loop: iterating on a service, deploying a new image from a laptop, inspecting logs, rolling back. Use it whenever the fastest correct step is one command.
- Terraform when the shape of a whole environment is the thing you are managing: several applications that reference each other, virtual networks, secrets and the ordering between them, reproduced per environment from one module.
- Both, commonly. Let Terraform own the structure and create the applications, and let CI deploy new images into them with the API or the CLI. Keep the image tag out of the Terraform-managed configuration if CI is what moves it, so the two do not overwrite each other's work.
Continue with Deploy from CI with the API, Deployments and rollback or API keys.