Infrastructure as Code with Terraform¶
This guide walks through the complete workflow for managing b'nerd Cloud resources with Terraform: installing the provider, authenticating, writing resource configurations, running plan/apply, and keeping state under control.
Prerequisites: Terraform >= 1.5 installed, a b'nerd Cloud account with an API token, and an organization + project already created (see First Project & API Token).
1. Install the Provider¶
Declare the provider in a versions.tf (or any .tf file) in your working directory:
terraform {
required_version = ">= 1.5"
required_providers {
bnerd = {
source = "bnerd-cloud/bnerd"
version = "~> 0.1"
}
}
}
Then initialize:
Terraform downloads the provider binary from the registry and locks the version in .terraform.lock.hcl. Commit the lock file; don't commit .terraform/.
2. Configure Authentication¶
The provider needs four values: API URL, bearer token, organization ID, and project ID. Supply them via environment variables to keep secrets out of source control:
export BNERD_API_URL="https://api.bnerd.cloud"
export BNERD_TOKEN="<your-api-token>"
export BNERD_ORG_ID="<org-uuid>"
export BNERD_PROJECT_ID="<project-uuid>"
Then declare the provider block with no hardcoded values:
Alternatively, pass values in HCL — useful when different workspaces target different projects:
provider "bnerd" {
api_url = var.bnerd_api_url
token = var.bnerd_token
org_id = var.org_id
project_id = var.project_id
}
Keep tokens out of state
API tokens passed via HCL variables end up in terraform.tfstate in plaintext. Use environment variables or a secrets manager (Vault, SOPS, GitLab CI variables) and reference them via TF_VAR_* instead.
3. Define Resources¶
Create a main.tf with the resources you want to manage. A typical starting point manages a project, object storage credentials, and a DNS zone:
# Project (if not already created out-of-band)
resource "bnerd_project" "main" {
name = "my-app"
organization_id = var.org_id
}
# RGW object storage user for the application
resource "bnerd_rgw_user" "app" {
name = "my-app-storage"
project_id = bnerd_project.main.id
}
# DNS zone
resource "bnerd_dns_zone" "primary" {
name = "example.com." # trailing dot required
kind = "Native"
nameservers = ["ns1.bnerd.net.", "ns2.bnerd.net."]
}
# A record for the web endpoint
resource "bnerd_dns_record" "www" {
zone_id = bnerd_dns_zone.primary.id
name = "www.example.com."
type = "A"
ttl = 300
records = ["203.0.113.10"]
}
Expose the S3 credentials as outputs so other systems can consume them:
output "s3_endpoint" { value = var.bnerd_api_url }
output "s3_access_key" { value = bnerd_rgw_user.app.access_key_id }
output "s3_secret_key" {
value = bnerd_rgw_user.app.secret_key
sensitive = true
}
RGW quota and locked fields
quota and locked on bnerd_rgw_user are computed/read-only — they are managed by platform admins and cannot be set via Terraform. If you need quota adjustments, contact your platform team.
4. Read Existing Infrastructure with Data Sources¶
If resources already exist (created via the dashboard or CLI), use data sources to reference them without importing or recreating them:
# Look up an existing network to attach a server to
data "bnerd_network" "mgmt" {
project_id = var.project_id
# filter by name in the consuming resource if the data source returns a list
}
# Look up an existing DNS zone
data "bnerd_dns_zone" "primary" {
name = "example.com."
}
# Add a record to the existing zone
resource "bnerd_dns_record" "api" {
zone_id = data.bnerd_dns_zone.primary.id
name = "api.example.com."
type = "CNAME"
ttl = 300
records = ["www.example.com."]
}
The full data source catalogue is in the Terraform Provider reference.
5. Plan and Apply¶
Preview what Terraform will create, change, or destroy:
Review the output carefully — resources marked + will be created, ~ will be updated in-place, -/+ will be destroyed and recreated. Apply when ready:
Terraform prompts for confirmation. Type yes to proceed. Pass -auto-approve only in CI pipelines where the plan has already been reviewed.
6. Manage State¶
Terraform stores the mapping between your configuration and real resources in terraform.tfstate. Keep this file safe:
- Never commit state to version control. Add
*.tfstateand*.tfstate.backupto.gitignore. - Use a remote backend for team workflows. GitLab's built-in HTTP backend is the simplest option for b'nerd projects:
terraform {
backend "http" {
# GitLab provides the URL, username, and password via CI variables:
# TF_HTTP_ADDRESS, TF_HTTP_USERNAME, TF_HTTP_PASSWORD
}
}
Run terraform init -reconfigure after adding a backend to migrate local state to the remote store.
- State locking is handled automatically by the GitLab backend — concurrent applies are blocked until the lock is released.
7. Import Existing Resources¶
If a resource was created outside Terraform (via the dashboard or CLI) and you want to bring it under management, use terraform import:
# Import an existing RGW user by its ID
terraform import bnerd_rgw_user.app <user-id>
# Import an existing DNS zone (supports org_id/zone_name or just zone_name)
terraform import bnerd_dns_zone.primary example.com.
After importing, run terraform plan to verify the state matches your configuration. Adjust any attributes that differ before committing.
8. Destroy¶
To remove all resources managed by a configuration:
Irreversible
Destroying an RGW user permanently deletes its access keys and all associated buckets and objects. Destroying a DNS zone removes all records. Always review the destroy plan before confirming.
Using Terraform Modules¶
For recurring infrastructure patterns — such as IPsec VPN gateways — use the b'nerd reusable modules:
module "ipsec_gateway" {
source = "git::https://git.bnerd.net/cloud/terraform/common.git//modules/ipsec-gateway?ref=v1.0.0"
prefix = "prod-gw"
router_id = data.openstack_networking_router_v2.router.id
network_id = "<management-network-uuid>"
subnet_id = "<management-subnet-uuid>"
floating_ip = "203.0.113.10"
image_id = data.openstack_images_image_v2.ubuntu.id
ssh_public_key = file("~/.ssh/id_ed25519.pub")
tunnels = [
{
name = "tunnel1"
local_cidr = "10.0.0.0/24"
remote_cidrs = ["192.168.10.0/24"]
},
]
}
See the Terraform Modules reference for the full ipsec-gateway input/output documentation.
Next Steps¶
- Terraform Provider reference — all resources and data sources
- Terraform Modules reference — reusable modules
- DNS Setup guide — configuring zones and records
- Provision Object Storage guide — RGW users, buckets, and keys