Portefaix docs GitHub

Deploy Portefaix on Azure

This guide shows you how to deploy a production-ready Portefaix platform on Microsoft Azure using AKS, Azure Workload Identity for keyless authentication, Azure Blob Storage for Terraform state, and GitHub Actions with OIDC federation for infrastructure automation.

Goal: a running AKS cluster with Portefaix stacks continuously reconciled by ArgoCD, using Azure Managed Identity bound to Kubernetes service accounts — no client secrets stored anywhere in the cluster.

Prerequisites

  • Azure subscription with Contributor or Owner role
  • az CLI v2.55+ authenticated (az login)
  • Terraform ≥ 1.5, kubectl, and Helm installed locally
  • Resource provider registrations: Microsoft.ContainerService, Microsoft.KeyVault, Microsoft.ManagedIdentity
  • A GitHub organisation for your GitOps repositories

1. Configure your environment

az login
az account set --subscription "YOUR_SUBSCRIPTION_NAME_OR_ID"

export AZURE_SUBSCRIPTION_ID="$(az account show --query id -o tsv)"
export AZURE_TENANT_ID="$(az account show --query tenantId -o tsv)"
export AZURE_LOCATION="westeurope"
export PORTEFAIX_ENV="staging"
export AZURE_RG="portefaix-$PORTEFAIX_ENV"

2. Register required resource providers

az provider register --namespace Microsoft.ContainerService
az provider register --namespace Microsoft.KeyVault
az provider register --namespace Microsoft.ManagedIdentity
az provider register --namespace Microsoft.Storage
az provider register --namespace Microsoft.Network

# Wait for registration (may take a few minutes)
az provider show --namespace Microsoft.ContainerService --query registrationState -o tsv

3. Create a service principal for Terraform

Create a service principal with Contributor scope on your subscription. Terraform uses this to provision Azure resources:

az ad sp create-for-rbac \
  --name "portefaix-terraform-$PORTEFAIX_ENV" \
  --role Contributor \
  --scopes "/subscriptions/$AZURE_SUBSCRIPTION_ID" \
  --output json

Export the returned values for use by Terraform:

export ARM_SUBSCRIPTION_ID="$AZURE_SUBSCRIPTION_ID"
export ARM_TENANT_ID="$AZURE_TENANT_ID"
export ARM_CLIENT_ID="<appId from output>"
export ARM_CLIENT_SECRET="<password from output>"

For production: replace the client secret with federated credentials (OIDC) so GitHub Actions can authenticate without storing any secret. The Terraform root module creates the federated credential configuration automatically.

4. Create Terraform remote state storage

Terraform state is stored in Azure Blob Storage with a dedicated resource group and storage account. Create these before running any other Terraform modules:

# Resource group for Terraform state
az group create \
  --name portefaix-tfstate \
  --location $AZURE_LOCATION

# Storage account (name must be globally unique, 3-24 lowercase alphanumeric)
az storage account create \
  --name portefaixtfstate \
  --resource-group portefaix-tfstate \
  --location $AZURE_LOCATION \
  --sku Standard_LRS \
  --allow-blob-public-access false \
  --min-tls-version TLS1_2

# Retrieve the storage account key
export AZ_STORAGE_ACCOUNT_KEY="$(az storage account keys list \
  --account-name portefaixtfstate \
  --resource-group portefaix-tfstate \
  --query '[0].value' -o tsv)"

# Create the container for state files
az storage container create \
  --name tfstate \
  --account-name portefaixtfstate \
  --account-key "$AZ_STORAGE_ACCOUNT_KEY"

5. Enable preview features

Some Portefaix features require Azure preview capabilities. Enable the AKS Workload Identity and OIDC issuer features:

az feature register \
  --namespace "Microsoft.ContainerService" \
  --name "EnableWorkloadIdentityPreview"

az feature register \
  --namespace "Microsoft.ContainerService" \
  --name "EnableOIDCIssuerPreview"

# Wait until both show "Registered"
az feature show \
  --namespace "Microsoft.ContainerService" \
  --name "EnableWorkloadIdentityPreview" \
  --query properties.state -o tsv

6. Provision the AKS cluster with Terraform

cd portefaix-infrastructure/terraform/azure/aks
cp terraform.tfvars.example terraform.tfvars

Key variables in terraform.tfvars:

subscription_id          = "your-subscription-id"
location                 = "westeurope"
resource_group           = "portefaix-staging"
cluster_name             = "portefaix-staging"
kubernetes_version       = "1.31"
oidc_issuer_enabled      = true
workload_identity_enabled = true
terraform init \
  -backend-config="resource_group_name=portefaix-tfstate" \
  -backend-config="storage_account_name=portefaixtfstate" \
  -backend-config="container_name=tfstate" \
  -backend-config="key=aks/$PORTEFAIX_ENV.tfstate"

terraform plan -out=tfplan
terraform apply tfplan

7. Configure Azure Workload Identity

Azure Workload Identity uses OIDC federation between Kubernetes service accounts and Azure Managed Identities — no client secrets enter the cluster. The Terraform module creates all Managed Identities and Federated Credentials automatically. Verify:

# Verify the Workload Identity mutating webhook is running
kubectl get pods -n azure-workload-identity-system

# Check Federated Credentials for a managed identity
az identity federated-credential list \
  --identity-name portefaix-external-secrets \
  --resource-group $AZURE_RG \
  --output table

8. Fetch cluster credentials

. ./portefaix.sh azure

az aks get-credentials \
  --resource-group $AZURE_RG \
  --name portefaix-$PORTEFAIX_ENV \
  --overwrite-existing

kubectl get nodes
NAME                           STATUS   ROLES    AGE   VERSION
aks-core-19506595-vmss000000   Ready    agent    8h    v1.31.0

9. Deploy Portefaix stacks via ArgoCD

helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

helm install argocd argo/argo-cd \
  --namespace argocd --create-namespace \
  --values portefaix-kubernetes/gitops/argocd/values-azure.yaml \
  --wait

kubectl apply -f portefaix-kubernetes/gitops/argocd/bootstrap/app-of-apps-azure-$PORTEFAIX_ENV.yaml

argocd app wait portefaix-bootstrap --health --timeout 600

Stacks available on Azure

StackDescriptionAzure service used
ObservabilityPrometheus, Grafana, Loki, TempoAzure Blob Storage for long-term storage
Secret managementExternal Secrets OperatorAzure Key Vault
DNS managementExternal DNSAzure DNS
TLS certificatescert-managerAzure DNS for DNS-01 challenges
Policy enforcementKyverno

Troubleshooting

Workload Identity webhook not injecting annotations

# Check the webhook configuration exists
kubectl get mutatingwebhookconfigurations | grep workload-identity

# Check the webhook pod logs
kubectl logs -n azure-workload-identity-system \
  -l app=workload-identity-webhook --tail=50

AKS OIDC issuer URL not set

az aks show \
  --resource-group $AZURE_RG \
  --name portefaix-$PORTEFAIX_ENV \
  --query oidcIssuerProfile.issuerUrl -o tsv

If empty, the cluster was created without the OIDC issuer. Enable it:

az aks update \
  --resource-group $AZURE_RG \
  --name portefaix-$PORTEFAIX_ENV \
  --enable-oidc-issuer \
  --enable-workload-identity