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
azCLI 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
| Stack | Description | Azure service used |
|---|---|---|
| Observability | Prometheus, Grafana, Loki, Tempo | Azure Blob Storage for long-term storage |
| Secret management | External Secrets Operator | Azure Key Vault |
| DNS management | External DNS | Azure DNS |
| TLS certificates | cert-manager | Azure DNS for DNS-01 challenges |
| Policy enforcement | Kyverno | — |
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