bahriya/library/terraform-provider
0 forks
Bahriya's Official Terraform Provider
id: 194
22,778 Lines
  • Go 85%
  • Markdown 14.1%
  • Shell 0.4%
  • Yaml 0.4%
README.md

terraform-provider-bahriya

The official Terraform provider for Bahriya — manage every Bahriya resource declaratively, from your first container to a multi-region production estate.

Status: published to the Terraform Registry as bahriya-cloud/bahriya.

What is Bahriya?

Bahriya is a container cloud with a simple contract: bring a container image, pick your regions, and the platform runs it — load balancing, TLS, health checks, autoscaling, a secrets vault and managed datastores included rather than bolted on. Everything the console can do, this provider can do in HCL, so your whole estate lives in version control.

Explore the platform:

ProductWhat it gives you
HTTP ContainersDeploy an image, get a TLS-terminated, autoscaling, multi-region service with custom hostnames.
WorkersLong-running background processes — queue consumers, stream processors — without ingress.
Cron JobsScheduled containers on a cron expression you control.
Managed ValkeyThe Redis-compatible, open-source in-memory datastore — single, highly available or sharded.
Managed MemcachedZero-ceremony caching next to your containers.
VaultSecrets, TLS bundles, SSH/GPG keypairs and encryption keys — versioned, rotatable, mountable.
ConfigsEnv files and YAML/JSON/plain config files, attached to projects and mounted into containers.
Network PoliciesDeclare which workloads may talk to what, ingress and egress.
Volume StoragePersistent volumes for stateful workloads.
Reis CLIThe same platform from your terminal — flags or YAML, ideal alongside Terraform in CI.
TerraformThis provider's product page — why infrastructure-as-code is a first-class citizen on Bahriya.

Learn and stay current:


Table of contents


Installation

From the Terraform Registry:

terraform {
  required_providers {
    bahriya = {
      source  = "bahriya-cloud/bahriya"
      version = "~> 0.3"
    }
  }
}

To build and run from source instead, see Building from source and Local development below.

Quickstart

terraform {
  required_providers {
    bahriya = { source = "bahriya-cloud/bahriya" }
  }
}

provider "bahriya" {}

data "bahriya_regions" "active" {
  status_filter = "active"
}

resource "bahriya_project" "web" {
  handle  = "web-prod"
  name    = "Web Production"
  regions = [data.bahriya_regions.active.regions[0].id]
}

resource "bahriya_registry" "ghcr" {
  handle   = "ghcr"
  name     = "GitHub Container Registry"
  server   = "ghcr.io"
  username = var.ghcr_username
  password = var.ghcr_token
}

resource "bahriya_secret" "db_password" {
  handle = "db-password"
  name   = "Database Password"
  value  = var.db_password
}

resource "bahriya_container" "api" {
  handle    = "api"
  name      = "API Server"
  image     = "ghcr.io/myorg/api:1.2.3"

  # Required for HTTP containers — deploy will fail without these
  containerport   = "3000"
  healthcheckpath = "/healthz"

  mincpu    = "250"
  minmemory = "256"

  autoscalingminreplicas = "2"
  activeregions          = [data.bahriya_regions.active.regions[0].id]
  project                = bahriya_project.web.id
  registry               = bahriya_registry.ghcr.handle

  secretsenvvar {
    secret = bahriya_secret.db_password.handle
    name   = "DATABASE_PASSWORD"
  }

  hostnames {
    hostname = "api.example.com"
  }
}

Provider configuration

AttributeRequiredEnv varDescription
tokenyesBAHRIYA_TOKENPersonal access token (PAT). Issued in the console under your profile.
organisation_idyesBAHRIYA_ORGANISATION_IDThe UUID of the org these resources belong to.
base_urlnoBAHRIYA_API_URLAPI root. Defaults to https://api.bahriya.cloud/console/v1.

Attributes on the provider block override env vars. For multi-org setups, use alias:

provider "bahriya" {
  alias           = "prod"
  organisation_id = var.prod_org_id
}

provider "bahriya" {
  alias           = "staging"
  organisation_id = var.staging_org_id
}

Authentication

The provider uses Bahriya personal access tokens (PATs):

  1. Sign in to the console at https://console.bahriya.cloud.
  2. Open your profile, then Tokens.
  3. Click Create token, copy the pat_... value.
export BAHRIYA_TOKEN="pat_..."
export BAHRIYA_ORGANISATION_ID="65cc42f1-..."

Resources

ResourceDescriptionE2E Verified
bahriya_projectLogical grouping of containers, secrets, registries; carries quota.Yes
bahriya_containerA workload: HTTP server, worker, or cronjob. Multi-region by default.Yes
bahriya_registryPrivate container registry credentials, scoped to an org.Yes
bahriya_secretEncrypted secret value, mountable as env var in a container.Yes
bahriya_memcachedManaged Memcached instance, attachable to containers as a cache backend.Yes
bahriya_tls_bundleTLS bundle (CA + cert + key), versioned with rotation history.Yes
bahriya_x509_certStandalone X.509 certificate (no key), versioned.Yes
bahriya_gpg_keypairGPG public + private keypair (OpenPGP armor), versioned.Yes
bahriya_ssh_keypairSSH public + private keypair, versioned.Yes
bahriya_encryption_keySymmetric encryption key (AES, ChaCha20, etc.), versioned.Yes
bahriya_env_fileKEY=VALUE env file. Injected into containers via envFrom.Yes
bahriya_yaml_configYAML config file, mounted into containers as a file.Yes
bahriya_json_configJSON config file, mounted into containers as a file.Yes
bahriya_plain_configArbitrary text config file, mounted into containers as a file.Yes
bahriya_network_policyIngress/egress network policy, attachable to projects and workloads.Yes
bahriya_valkeyManaged Valkey instance — cache or persistent store; single, HA or sharded.Yes
bahriya_path_rulePath-scoped HTTP control on a container: basic auth, rate limit, IP allow/deny.Yes
bahriya_billing_detailsThe organisation's billing identity printed on invoices (singleton).Yes
bahriya_roleCustom organisation role — a named set of permission grants.Yes
bahriya_resource_grantShares a specific resource instance with an org member (instance ACL).Yes

Each vault/config item also has a corresponding bahriya_project_<type>_attachment resource that binds an item to a project. Once attached, the item can be referenced from a bahriya_container block to mount it inside the container.

All resource schemas are code-generated from the OpenAPI specs at packages/bahriya-openapi/specs/v1/.

The bahriya_organisation and bahriya_regions data sources are also E2E verified.

End-to-end coverage

examples/e2e-vault-configs/ is the comprehensive E2E: in a single apply/destroy it exercises every resource above plus both data sources — registry + secret and all vault/config items with their project attachments, a network policy, memcached, a single-tier valkey, and HTTP + worker + cronjob containers wired to the full set of mounts, env vars, secret env vars, persistent storage and path rules. Run ./gen-fixtures.sh (once) to mint crypto fixtures and a fresh handle suffix, then terraform apply.

Secret-sync timing. A container that consumes a secret (via secretsenvvar) or an env file (via envfiles/envFrom) will only reach running once the project's bahriya-secrets bundle has synced into the namespace. On a brand-new project the first container deploy can lose that race and land in error; re-running the apply (once the sync has completed) brings it to running. This is a platform deploy-pipeline ordering concern, not a provider behaviour — the provider creates and attaches the secret and orders the container after it.

Minimum required fields

These are the fields you must set for a successful deploy. Omitting any of these will cause errors (API rejection or runtime health-check failures).

bahriya_container (HTTP)

FieldTypeExampleNotes
handlestring"api"Immutable. Cannot be reused after delete (soft-delete).
namestring"API Server"
imagestring"nginx:alpine"Full image ref including tag.
containerportstring"8080"The port your application listens on.
healthcheckpathstring"/healthz"HTTP path for liveness/readiness probes. Deploy will fail if the container does not respond 200 at this path on containerport.
mincpustring"100"Millicores.
minmemorystring"128"Megabytes.
autoscalingminreplicasstring"1"
activeregionslist["falkenstein-1"]At least one region.
projectstringbahriya_project.x.idMust be the project UUID (.id), not the handle.

bahriya_container (worker / cronjob)

Same as HTTP except containerport and healthcheckpath are not required (workers have no ingress).

bahriya_memcached

FieldTypeExampleNotes
handlestring"session-cache"Immutable. Cannot be reused after delete.
namestring"Session Cache"
memorymbnumber512Cache size in megabytes (integer, not string).
activeregionslist["helsinki-1"]At least one region.
projectstringbahriya_project.x.idMust be the project UUID (.id), not the handle.

bahriya_project

FieldTypeExampleNotes
handlestring"web-prod"Immutable. Soft-deleted.
namestring"Web Production"
regionslist["falkenstein-1"]At least one region.

bahriya_registry

FieldTypeExampleNotes
handlestring"ghcr"Released on delete (reusable).
namestring"GHCR"
serverstring"ghcr.io"Registry hostname.
usernamestring"myuser"
passwordstring(sensitive)

bahriya_secret

FieldTypeExampleNotes
handlestring"db-password"Released on delete (reusable).
namestring"DB Password"
valuestring(sensitive)

Vault items

All vault types require handle and name plus the content fields below. Handles are immutable. Items are soft-deleted (handle burned).

ResourceRequired content fieldsNotes
bahriya_tls_bundleca, cert, keyPEM-encoded. key is sensitive.
bahriya_x509_certcertPEM-encoded standalone cert (no key).
bahriya_gpg_keypairpublic_key, private_keyOpenPGP armored. Both fields marked sensitive by the spec.
bahriya_ssh_keypairpublic_key, private_keyOpenSSH format. Both fields marked sensitive by the spec.
bahriya_encryption_keyformat, keyformatbase64, hex, raw. key is sensitive.

The API parses content on create / rotate — invalid material is rejected.

Config items

ResourceRequired content fieldsNotes
bahriya_env_filecontentKEY=VALUE lines. Injected via envFrom.
bahriya_yaml_configcontentValid YAML.
bahriya_json_configcontentValid JSON.
bahriya_plain_configcontentArbitrary text. Mounted as a file.

Networking

ResourceNotable fieldsNotes
bahriya_network_policyingresspeers, egresscidrs, egressfqdns, portsAll optional. Any egresscidrs flips the target to deny-by-default outbound. egressfqdns is stored/validated now, enforced later. ports is a nested list of { port, protocol }.

Project attachments

Each vault/config type has a corresponding attachment resource (bahriya_project_tls_bundle_attachment, etc.). All take exactly two required fields:

FieldTypeDescription
project_idstringUUID of the project (bahriya_project.x.id).
handlestringHandle of the item to attach.

Both attributes force replacement on change. Attachment is also blocked at the API if a container in the project still references the item.

Data sources

Data sourceDescription
bahriya_organisationFetches the current organisation (id, name, handle, email, role).
bahriya_regionLook up a single region by ID (name, status, location).
bahriya_regionsList all regions, with optional status_filter.

Important behaviours

Handles cannot be reused (soft-delete)

For projects, containers, and memcached, deleting in Terraform does not free up the handle. These resources are soft-deleted for billing and audit reasons.

  • Use distinct handles per environment (api-prod, api-staging).
  • Treat handles as permanent identifiers.

For registries and secrets, handles are released on delete and can be reused.

Async termination

Destroying a bahriya_container or bahriya_memcached triggers async termination. The provider polls until the resource reaches terminated status (default timeout: 10 minutes).

Renaming a handle is destroy + recreate

Handles are immutable. Changing the handle attribute forces Terraform to destroy and recreate the resource.

Imports use UUIDs

terraform import bahriya_container.api 065df92e-4e46-436a-a0a0-aaaaaaaaaaaa

Sensitive fields

Registry password and secret value are marked sensitive. The API returns masked sentinels on read; the provider preserves the real value from your Terraform state.

Vault item content fields (bahriya_tls_bundle.key, bahriya_gpg_keypair.private_key / .public_key, bahriya_ssh_keypair.private_key / .public_key, bahriya_encryption_key.key) are also sensitive and follow the same masking pattern.

Removing a nested attachment block must be explicit

Container nested-list attributes (tls_bundles, x509_certs, gpg_keypairs, ssh_keypairs, encryption_keys, env_files, yaml_configs, json_configs, plain_configs, persistentvolumes, hostnames, initjobs, secretsenvvar, newenvvar) are Optional+Computed with the UseStateForUnknown plan modifier — needed for stable plans against the API's PUT-replace semantics. As a consequence, omitting a block in your .tf does NOT remove the attachment; the prior state value is re-injected silently.

To remove a vault/config attachment from a container, set the block to an explicit empty list:

resource "bahriya_container" "api" {
  # ...
  json_configs = []   # explicit removal — works
  # plain_configs    # omitting — does NOT remove; prior list is preserved
}

Detach guard

Attempting to detach a vault/config item from a project while a container in that project still references it returns 409 CONFLICT with the offending container's handle. Update the container to remove the reference first.

Container Update waits for status

After a terraform apply that modifies a container, the provider waits for the container to reach running (or terminal error) before completing the apply. This avoids "inconsistent result after apply" errors when the post-PUT fetch would otherwise race the helm rollout.

Common pitfalls

SymptomCauseFix
Container reaches error status after deployMissing or wrong healthcheckpath. The platform probes containerport + healthcheckpath to decide if the container is healthy.Set healthcheckpath to a path your app responds 200 on (e.g. / or /healthz).
project attribute rejectedPassing the project handle instead of UUID.Use bahriya_project.x.id, not .handle.
terraform plan shows unknown fieldUsing snake_case names (container_port, min_cpu). The provider uses flat lowercase names from the API.Use containerport, mincpu, minmemory, activeregions, etc.
Handle already taken after destroySoft-deleted resources don't release handles.Use a new handle (e.g. api-v2).

Examples

Runnable configs live under examples/:

DirectoryWhat it demonstrates
project-only/Minimal project resource
registry-secret/Registry + secret with variable inputs
data-sources/All 3 data sources (organisation, region, regions)
container/Full container deployment with registry, secrets, hostname
memcached/Cache instance inside a project
complete/Everything together

Building from source

Requires Go 1.23+.

git clone <repo-url>
cd terraform-provider-bahriya
make build        # builds ./bin/terraform-provider-bahriya
make test         # runs all 63 unit tests
make generate     # regenerates resource code from OpenAPI specs
make verify       # generate + git diff (CI gate)

Local development

Add a dev_overrides block to ~/.terraformrc:

provider_installation {
  dev_overrides {
    "bahriya-cloud/bahriya" = "/path/to/terraform-provider-bahriya/bin"
  }
  direct {}
}

Then just make build and run terraform plan/apply — no terraform init needed between rebuilds.

Set credentials via environment variables:

export BAHRIYA_TOKEN="pat_..."
export BAHRIYA_ORGANISATION_ID="..."
# Optional: override API URL for dev
export BAHRIYA_API_URL="http://localhost:8080/console/v1"

Architecture

This is a code-generated provider. The OpenAPI specs at packages/bahriya-openapi/specs/v1/ are the source of truth. A Go generator reads those specs and emits Terraform resources with full CRUD, ImportState, diff short-circuit, and status polling.

What's generated

  • All 5 resource files (internal/resources/*_resource.go) — ~3,800 LOC total
  • Resource registry (internal/resources/registry.go)
  • Terraform schemas with nested attributes for complex types (Container has 6 nested object types)
  • Plan modifiers (RequiresReplace for handles, UseStateForUnknown for computed fields)
  • Sensitive field preservation (password, value)

What's hand-written

  • Provider scaffold (internal/provider/) — schema, Configure, env-var fallbacks
  • HTTP client (internal/client/) — PAT auth, retries, typed errors
  • Data sources (internal/datasources/) — organisation, region, regions
  • Transforms (internal/transforms/) — envvar/secret dict-to-array, x-implies
  • Lifecycle (internal/lifecycle/) — WaitForStatus with backoff
  • Helpers (internal/resources/helpers.go) — small utilities for generated code
  • Code generator (cmd/generator/) — parser, model, renderer, template

Generated files are committed to git. make verify fails CI if they drift.

Releasing

The source of truth is on 1x.ax (origin). GitHub (github) is a mirror that feeds the Terraform Registry. Push to both:

# Push code
git push origin && git push github

# Push tags (triggers the GitHub Actions release workflow)
git push origin --tags && git push github --tags

To cut a release:

git tag v0.1.0
git push origin && git push origin --tags && git push github && git push github --tags

The GitHub Actions workflow runs GoReleaser, builds cross-platform binaries, signs the checksums with GPG, and publishes the release. The Terraform Registry picks it up automatically.

Repository layout

terraform-provider-bahriya/
├── cmd/
│   ├── terraform-provider-bahriya/   # plugin entrypoint
│   └── generator/                    # codegen tool
│       ├── templates/                # text/template for resources
│       └── parser_test.go            # generator unit tests
├── internal/
│   ├── client/       # HTTP client + tests
│   ├── provider/     # provider block + tests
│   ├── resources/    # GENERATED — do not edit by hand
│   ├── datasources/  # data sources + tests
│   ├── transforms/   # envvar, secret, implies + tests
│   └── lifecycle/    # WaitForStatus + tests
├── examples/         # 6 runnable Terraform configs
├── Makefile
├── go.mod
└── .gitignore

Testing

63 unit tests across all packages:

make test           # runs go test ./internal/... and cmd/generator
PackageTestsCoverage
cmd/generator16Parser, model helpers, type resolution, nested $ref
internal/client10HTTP envelope, auth, retry, error types
internal/datasources17Metadata, schema, configure, response parsing
internal/provider5Metadata, schema, resource counts, firstNonEmpty
internal/lifecycle5Wait-for-status (target, terminal, timeout, cancel)
internal/transforms11Envvar, secret, implies round-trips
Please wait...
Connection lost or session expired, reload to recover
Page is in error, reload to recover