Azure deployment
Owner: Cloud Ops
Returns to: 3. Configure & install Helm
This path provisions AKS, Azure Database for PostgreSQL Flexible Server, Azure Key Vault, AKS Workload Identity, ingress-nginx, cert-manager, and a static public IP.
1. Get the Terraform release
Section titled “1. Get the Terraform release”Clone the public Terraform mirror and check out the chaindb-vX.Y.Z release
tag supplied by Arbium. Keep the Terraform tag aligned with the Helm chart and
runtime image version; do not deploy from an unpinned main branch.
git clone https://github.com/try-caret/arbium-terraform.gitcd arbium-terraformexport ARBIUM_TERRAFORM_DIR="$PWD"git checkout chaindb-vX.Y.Zcd azure/customercp envs/example.tfvars envs/<environment>.tfvarsaz loginaz account set --subscription <subscription-id>az aks install-cli2. Prepare remote state and providers
Section titled “2. Prepare remote state and providers”Create an Azure Storage account and blob container for Terraform state in a
resource group outside the Arbium deployment resource group. Enable blob
versioning and grant the deployer Storage Blob Data Contributor.
Choose a globally unique, 3–24 character lowercase storage account name:
export TFSTATE_RESOURCE_GROUP=arbium-tfstateexport TFSTATE_STORAGE_ACCOUNT="<globally-unique-storage-account-name>"export TFSTATE_LOCATION="<azure-region>"
az group create \ --name "$TFSTATE_RESOURCE_GROUP" \ --location "$TFSTATE_LOCATION"
az storage account create \ --name "$TFSTATE_STORAGE_ACCOUNT" \ --resource-group "$TFSTATE_RESOURCE_GROUP" \ --location "$TFSTATE_LOCATION" \ --sku Standard_LRS \ --kind StorageV2 \ --min-tls-version TLS1_2 \ --https-only true \ --allow-blob-public-access false
az storage account blob-service-properties update \ --account-name "$TFSTATE_STORAGE_ACCOUNT" \ --resource-group "$TFSTATE_RESOURCE_GROUP" \ --enable-versioning true
export DEPLOYER_OBJECT_ID="$(az ad signed-in-user show --query id -o tsv)"export TFSTATE_ACCOUNT_ID="$(az storage account show \ --name "$TFSTATE_STORAGE_ACCOUNT" \ --resource-group "$TFSTATE_RESOURCE_GROUP" \ --query id -o tsv)"
az role assignment create \ --assignee-object-id "$DEPLOYER_OBJECT_ID" \ --assignee-principal-type User \ --role "Storage Blob Data Contributor" \ --scope "$TFSTATE_ACCOUNT_ID"
az storage container create \ --account-name "$TFSTATE_STORAGE_ACCOUNT" \ --name tfstate \ --auth-mode loginFor an automation identity, set DEPLOYER_OBJECT_ID to its service-principal
object ID and use the matching --assignee-principal-type. Azure role
assignments can take several minutes to propagate; retry the container command
if it initially returns an authorization error.
The public Terraform release intentionally excludes environment-specific
backend wiring. Create an untracked backend.tf in azure/customer:
terraform { backend "azurerm" {}}Create the backend directory, then add
backends/<environment>.azurerm.hcl with Azure AD authentication:
mkdir -p backendsresource_group_name = "<terraform-state-resource-group>"storage_account_name = "<terraform-state-account>"container_name = "tfstate"key = "azure/customer/<environment>/terraform.tfstate"use_azuread_auth = trueDo not commit either file. Confirm during terraform init that Terraform says
it configured the azurerm backend. A Missing backend configuration warning
means backend.tf is absent and Terraform is using local state; stop before
applying.
Register the resource providers used by the foundation:
for rp in Microsoft.Network Microsoft.ContainerService \ Microsoft.DBforPostgreSQL Microsoft.KeyVault \ Microsoft.ManagedIdentity Microsoft.Compute Microsoft.Storage; do az provider register --namespace "$rp" --waitdone3. Configure the Azure foundation
Section titled “3. Configure the Azure foundation”Set the subscription, tenant, region, non-overlapping network ranges, AKS node
sizing, and PostgreSQL protection in the copied .tfvars file.
subscription_id = "<azure-subscription-id>"tenant_id = "<entra-tenant-id>"location = "centralus"environment = "<environment>"name_prefix = "arbium"
general_node_min_count = 1general_node_max_count = 3
postgres_backup_retention_days = 7
secret_names = [ "scheduler", "registry", "gemini", "enrollment", "jwt", "license"]
# Add "scim" when SCIM provisioning is enabled. Also add# "admin-client-id" and "admin-client-secret" when enabling the fleet console.
ingress_static_ip_enabled = trueConfirm that the selected region offers both the configured AKS VM SKU and PostgreSQL Flexible Server SKU for the customer subscription:
export AZURE_LOCATION=centralusexport AKS_VM_SIZE=Standard_D2as_v7
az aks get-versions --location "$AZURE_LOCATION" --output tableaz vm list-skus \ --location "$AZURE_LOCATION" \ --resource-type virtualMachines \ --size "$AKS_VM_SIZE" \ --output tableaz postgres flexible-server list-skus \ --location "$AZURE_LOCATION" \ --output tableThe PostgreSQL CLI lists the Burstable offering as Standard_B1ms; the
equivalent Terraform input is B_Standard_B1ms. If the intended Kubernetes
version or either SKU is missing or restricted, choose a supported combination
before applying. Keep secret values out of .tfvars.
4. Plan and apply
Section titled “4. Plan and apply”terraform init -reconfigure \ -backend-config=backends/<environment>.azurerm.hclterraform fmt -recursiveterraform validateterraform plan -var-file=envs/<environment>.tfvarsterraform apply -var-file=envs/<environment>.tfvarsThe apply creates the resource group, VNet, AKS, private PostgreSQL server, Key Vault, workload identity, static ingress IP, External Secrets Operator, ingress-nginx, cert-manager, and reloader.
5. Configure cluster access
Section titled “5. Configure cluster access”$(terraform output -raw get_credentials_command)kubectl get nodeskubectl get pods -n external-secretskubectl get pods -n ingress-nginxkubectl get pods -n cert-manager6. Populate Azure Key Vault
Section titled “6. Populate Azure Key Vault”Terraform writes the database connection URL and PostgreSQL admin password to Key Vault. Populate the remaining values through the customer’s approved secret workflow:
| Key Vault secret | Value |
|---|---|
arbium-<env>-scheduler |
Random scheduler token |
arbium-<env>-enrollment |
Random enrollment secret |
arbium-<env>-jwt |
Random 32-byte JWT secret |
arbium-<env>-gemini |
Approved Gemini API key, when enabled |
arbium-<env>-license |
Arbium license key |
arbium-<env>-registry |
JSON {"username":"...","token":"..."} for GHCR |
arbium-<env>-scim |
Random SCIM bearer token, when SCIM is enabled |
arbium-<env>-admin-client-id |
Arbium Admin app client ID, when enabled |
arbium-<env>-admin-client-secret |
Arbium Admin app secret, when enabled |
Keep Key Vault values out of source control, Terraform variable files, and ticket comments.
7. Configure DNS and certificate resources
Section titled “7. Configure DNS and certificate resources”For production, create an A record for the customer hostname pointing at:
terraform output -raw ingress_ipKeep the record DNS-only while Let’s Encrypt performs HTTP-01 validation.
Replace the operations email in manifests/cluster-issuer.yaml, then apply the
cluster-scoped issuer from the Terraform checkout:
kubectl apply -f \ "$ARBIUM_TERRAFORM_DIR/azure/customer/manifests/cluster-issuer.yaml"Replace <ingress-host> in manifests/tls-certificate.yaml with the same
customer hostname. For a temporary smoke test without customer DNS, you may
instead use terraform output -raw ingress_host, which returns an
<ingress-ip>.sslip.io hostname. Do not treat that generated hostname as the
production endpoint. Apply the Certificate after Helm creates the arbium
namespace.
8. Create the Azure provider values
Section titled “8. Create the Azure provider values”Create ~/arbium-install and save this as
~/arbium-install/<environment>.azure.values.local.yaml, outside source
control. Omit extraSecrets when neither SCIM nor the fleet console is enabled;
omit only admin-ui-oidc when SCIM is enabled without the fleet console.
global: region: <azure-region>
imagePullSecrets: - name: ghcr-pull
externalSecrets: enabled: true provider: azurekv vaultUrl: <terraform-output-key_vault_uri> tenantId: <entra-tenant-id> serviceAccount: annotations: azure.workload.identity/client-id: <terraform-output-eso_identity_client_id> dataMappings: DATABASE_URL: arbium-<env>-database-url SUPABASE_DB_URL: arbium-<env>-database-url CHAINDB_SCHEDULER_TOKEN: arbium-<env>-scheduler ARBOR_AGENT_ENROLLMENT_SECRET: arbium-<env>-enrollment ROOTS_INTUNE_PILOT_ENROLLMENT_SECRET: arbium-<env>-enrollment GEMINI_API_KEY: arbium-<env>-gemini JWT_SECRET: arbium-<env>-jwt ARBIUM_LICENSE_KEY: arbium-<env>-license imagePullSecret: enabled: true secretId: arbium-<env>-registry targetSecretName: ghcr-pull extraSecrets: scim-bearer: SCIM_BEARER_TOKEN: arbium-<env>-scim admin-ui-oidc: OIDC_CLIENT_ID: arbium-<env>-admin-client-id OIDC_CLIENT_SECRET: arbium-<env>-admin-client-secret
secrets: create: false existingSecret: chaindb-runtime
license: existingSecret: chaindb-runtime
cloudSqlProxy: enabled: false
config: databaseSsl: require databaseTlsCaStore: system
ingress: enabled: true host: arbium.<customer-domain>
embedder: enabled: true gpu: enabled: falseThe supplied Azure preset uses the CPU embedder because the Terraform baseline creates one general-purpose node pool. A GPU deployment requires an additional AKS GPU pool plus matching node selectors and tolerations.
9. Record the provider outputs
Section titled “9. Record the provider outputs”terraform output cluster_nameterraform output key_vault_nameterraform output key_vault_uriterraform output eso_identity_client_idterraform output database_url_secret_nameterraform output ingress_ipContinue to 3. Configure & install Helm. After Helm creates the namespace, apply the certificate:
kubectl apply -n arbium -f \ "$ARBIUM_TERRAFORM_DIR/azure/customer/manifests/tls-certificate.yaml"