Deployment
Before deploying the Authorization Hub, ensure the following prerequisites are met:
-
KubernetesOpens in a new tab
A running K8s cluster (version 1.25 recommended) is required for deployment. This can be a local Minikube cluster for testing or a cloud cluster for production.
-
HelmOpens in a new tab
Helm version 3.8 must be installed locally to manage the charts.
-
kubectlOpens in a new tab
Installed and configured to access your K8s cluster.
-
PostgreSQLOpens in a new tab (optional)
An external Postgres instance. If you don't have one, a local Postgres pod can be provisioned, as described in the Deployment section.
-
Sufficient resources
A minimum of 4 CPUs and 8GB of RAM is required for local clusters running the full microservices stack. The ADM pod defaults to a 2GB memory limit, which supports ALFA policies up to approximately 8MB. If your policy files exceed this size, increase the ADM pod memory limit and ensure the cluster has sufficient memory.
-
Authorization Hub license
Contact the Axiomatics Customer SupportOpens in a new tab to obtain a license file (
axiomatics_HUB.license) for your deployment.
Preparation
The Authorization Hub is downloaded using the AWS CLI. If you have not already done so, install AWS CLI following the instructions in AWS documentation.
-
Configure the Axiomatics AWS CLI account using the
aws configurecommand, as explained in the AWS documentation. This procedure requires an Access key ID and a Secret access key, which will have been provided to you by Axiomatics. -
Download the Authorization Hub distribution.
aws s3api get-object --bucket axiomatics-customer-artifacts --key releases/com/axiomatics/hub/26.1.3/hub-26.1.3.zip hub-26.1.3.zip -
Log in to the Amazon ECR registry provided by Axiomatics.
aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 748131003707.dkr.ecr.eu-central-1.amazonaws.com
Deployment
Replace all $CHANGE_ME placeholders in the commands below with your actual values before running them.
-
Copy
axiomatics_HUB.licenseinto the/configurationsdirectory. -
Create a secret to pull the images from the ECR registry.
kubectl create secret docker-registry regcred \--docker-server=748131003707.dkr.ecr.eu-central-1.amazonaws.com \--docker-username=AWS --docker-password=$(aws ecr get-login-password --region eu-central-1) -
Create a secret containing values for the Keycloak pod.
kubectl create secret generic keycloak \--from-literal=hub_client_secret=$CHANGE_ME \--from-literal=principal_client_secret=$CHANGE_ME -
(Optional) Configure SMTP to enable email delivery.
-
Enable SMTP in
values.yamlby setting the following keys totrue:services:keycloak:smtp:enabled: truesmtpAuth: truesmtpStartTlsEnabled: truesmtpStartTlsRequired: trueservices:hubService:smtp:enabled: true -
Create a secret containing the SMTP credentials:
kubectl create secret generic smtp \--from-literal=host=$CHANGE_ME \--from-literal=port=587 \--from-literal=user=$CHANGE_ME \--from-literal=password=$CHANGE_ME \--from-literal=from=$CHANGE_ME -
Optionally, for local testing, enable emails using a test Gmail SMTP (
smtp.gmail.com) app through https://myaccount.google.com/apppasswords.Opens in a new tabnoteWe recommend using port 587 but you may configure an alternative port based on your requirements.
-
-
Create a secret with the PostgreSQL database credentials.
kubectl create secret generic db-connection \--from-literal=dbusername=$CHANGE_ME \--from-literal=dbpassword=$CHANGE_ME \--from-literal=dbhost=postgres \--from-literal=dbport=5432 -
Create a secret with the Tenant admin credentials. Authorization Hub bootstraps this user on first startup.
warningThe password must comply with the Password policy.
kubectl create secret generic principal \--from-literal=tenant_admin_email=$CHANGE_ME \--from-literal=tenant_admin_password=$CHANGE_ME -
Navigate to
kubernetes/chartsand install the Helm chart.helm install hub \-f hub/values.yaml hub \--set registry=748131003707.dkr.ecr.eu-central-1.amazonaws.com/axiomatics/ \--set hubHostname=$CHANGE_ME \--set 'imagePullSecrets[0].name=regcred'If you don't have an external database, add
-f hub/values-local-database.yamlafter the second line to deploy a PostgreSQL pod automatically.
Local testing deployment
Replace all $CHANGE_ME placeholders in the commands below with your actual values before running them.
For testing purposes only, you can run a deployment locally using minikube. First step is to start minikube.
minikube start --memory=8000 --cpus=4
Follow the steps in the Deployment section above, but skip the Helm chart installation. Then, proceed with the following steps.
Enable TLS (optional)
This section is optional. TLS is required when running Authorization Hub on a hostname other than localhost. If you are using localhost, skip this section as the HTTP configuration is sufficient.
To enable TLS, an Ingress controller is needed to terminate HTTPS before traffic reaches the Authorization Hub services. The following steps use the NGINX Ingress controller provided by Minikube.
-
While Minikube is running, enable the NGINX Ingress controller.
minikube addons enable ingress -
Generate a self-signed TLS certificate for your hostname.
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \-days 3650 -nodes \-keyout hub.key -out hub.crt \-subj "/CN=$CHANGE_ME" \-addext "subjectAltName=DNS:$CHANGE_ME" -
Create a Kubernetes secret containing the TLS certificate.
kubectl create secret tls hub-ingress-tls \--key hub.key \--cert hub.crt -
Make your hostname resolvable from the browser.
- Linux/macOS
- Windows
*:first-child]:mt-0>Add the hostname to
/etc/hostspointing to the Minikube node IP.echo "$(minikube ip) $CHANGE_ME" | sudo tee -a /etc/hosts*:first-child]:mt-0 hidden>Add your hostname to the Windows hosts file pointing to localhost.
-
Open
C:\Windows\System32\drivers\etc\hostsas Administrator and add:127.0.0.1 $CHANGE_ME -
Patch CoreDNS so that Authorization Hub services can resolve the hostname inside the cluster.
-
Get the NGINX ingress ClusterIP.
kubectl get svc ingress-nginx-controller -n ingress-nginx -
Edit the CoreDNS ConfigMap.
kubectl edit configmap coredns -n kube-system -
Find the existing hosts block and add your entry:
hosts {192.168.49.1 host.minikube.internal<CLUSTER-IP> $CHANGE_MEfallthrough}Replace
<CLUSTER-IP>with the value from the previous command. -
Save and restart CoreDNS.
kubectl rollout restart deployment coredns -n kube-system
-
Helm chart installation adjustments
Select the option that matches your setup: accessing Authorization Hub through localhost without TLS, or accessing it through a non-localhost hostname with TLS.
- Without TLS
- With TLS
-
Navigate to
kubernetes/chartsand install the Helm chart.helm install hub \-f hub/values.yaml hub \--set registry=748131003707.dkr.ecr.eu-central-1.amazonaws.com/axiomatics/ \--set hubHostname=localhost \--set hubProtocol=http \--set services.apiGateway.service.type=LoadBalancer \--set 'imagePullSecrets[0].name=regcred' -
Start the minikube tunnel in a separate terminal window and keep it running:
minikube tunnelAuthorization Hub is now available at
http://localhost.
Navigate to kubernetes/charts and install the Helm chart.
helm install hub \
-f hub/values.yaml \
-f hub/values-local-tls.yaml hub \
--set registry=748131003707.dkr.ecr.eu-central-1.amazonaws.com/axiomatics/ \
--set hubHostname=$CHANGE_ME \
--set hubProtocol=https \
--set 'imagePullSecrets[0].name=regcred'
values-local-tls.yaml enables the NGINX ingress and scopes trustedProxies to the minikube pod CIDR (10.244.0.0/16 by default). This is required because the NGINX Ingress addon's hostNetwork mode can cause iptables SNAT to inject pod network IPs into forwarding headers, triggering false session-binding mismatches. If you configured a custom --pod-network-cidr, update trustedProxies in values-local-tls.yaml accordingly. You can verify your pod CIDR with:
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}'
- Linux/macOS
- Windows
The NGINX ingress controller is directly accessible at the Minikube node IP — no tunnel is required. Authorization Hub is available at the hostname you configured in the previous steps.
The Minikube node IP is not directly accessible from the host. Run the following command in a separate Administrator terminal and keep it running:
kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller 443:443
Authorization Hub is available at the hostname you configured in the previous steps.
Your browser will show a warning for the self-signed certificate. This is expected. Accept the warning to continue.
Cloud deployment
This guide covers deploying Authorization Hub on any managed Kubernetes platform, such as EKS, GKE, AKS, OpenShift, and others. Each step describes what is required, followed by an AWS CLI example you can adapt to your provider.
The AWS examples are CLI-based reference commands. In your environment, you can use Infrastructure as Code (Terraform, CloudFormation), manual steps in the cloud console, or any equivalent method.
Requirements
| Requirement | Details |
|---|---|
| Managed Kubernetes | version ≥ 1.25 |
| Helm | ≥ 3.10 |
| kubectl | configured against the target cluster |
| Managed PostgreSQL | version ≥ 17, SSL/TLS enforced, encrypted storage, automated backups |
| DNS zone | you control |
| TLS certificate | matching the Hub hostname |
| Container registry access | credentials for the Authorization Hub image registry |
| License file | axiomatics_HUB.license provided by Axiomatics |
Variables
Export the following variables to your shell before starting as they are referenced throughout this guide.
export CLUSTER_NAME=hub-test
export DB_PASSWORD="$(openssl rand -base64 24 | tr -d '=+/')"
export TENANT_ADMIN_EMAIL="<your-email@example.com>"
export LICENSE_PATH="<path-to>/axiomatics_HUB.license"
# Filled in before Steps 5 and 8
export HUB_HOST=hub.yourdomain.com
export HOSTED_ZONE_ID=<Zxxxxxxxxxxxxx>
Step 1: Managed Kubernetes cluster
Set up a managed Kubernetes cluster with the following minimum requirements:
- Version: Kubernetes ≥ 1.25
- Node capacity: ≥ 2 worker nodes totaling ~16 GiB RAM / 4 vCPU
- Networking: nodes in private subnets with outbound internet access via NAT for image pulls
- RBAC: enabled (default on all managed offerings)
- OIDC provider: enabled (required for workload identity (IRSA, GKE Workload Identity, AKS Managed Identity))
- Autoscaling: node-group autoscaling recommended (min 2, max ≥ 4)
- CSI driver: a dynamic block-storage CSI driver installed (EBS, PD, or Azure Disk)
Managed Kubernetes is available from AWS (EKS), GCP (GKE), and Azure (AKS).
AWS example
cat > /tmp/eks-hub.yaml <<EOF
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: ${CLUSTER_NAME}
region: ${AWS_REGION}
version: "1.30"
iam:
withOIDC: true
managedNodeGroups:
- name: ng-default
instanceType: t3.large
desiredCapacity: 2
minSize: 2
maxSize: 4
volumeSize: 30
privateNetworking: true
addons:
- name: vpc-cni
- name: coredns
- name: kube-proxy
- name: aws-ebs-csi-driver
EOF
eksctl create cluster -f /tmp/eks-hub.yaml
aws eks update-kubeconfig --name "$CLUSTER_NAME" --region "$AWS_REGION"
EKS cluster creation using eksctl (takes approximately 15 minutes).
Step 2: Managed PostgreSQL database
Set up a managed PostgreSQL instance with the following requirements:
- Version: PostgreSQL ≥ 17
- Network: reachable only from the cluster's private network - no public endpoint
- Encryption: at rest (storage encryption) and in transit (SSL/TLS enforced)
- Sizing: ≥ 2 vCPU / 4 GiB RAM for initial production; tune based on load
- Backups: automated daily backups, ≥ 7-day retention, point-in-time recovery enabled
- High availability: multi-AZ or equivalent recommended for production
- User: a single DB user with ownership of all three Authorization Hub databases (
hub,keycloak,domain_manager)
Managed PostgreSQL is available from AWS (Amazon RDS), GCP (Cloud SQL), and Azure (Azure Database for PostgreSQL – Flexible Server).
Do not use the in-cluster Postgres pod from values-local-database.yaml for anything beyond local development or PoCs.
AWS example
VPC_ID=$(aws eks describe-cluster --name "$CLUSTER_NAME" \
--query "cluster.resourcesVpcConfig.vpcId" --output text)
NODE_SG=$(aws eks describe-cluster --name "$CLUSTER_NAME" \
--query "cluster.resourcesVpcConfig.clusterSecurityGroupId" --output text)
# Private subnets tagged by eksctl for internal load balancers
SUBNET_ARR=( $(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$VPC_ID" \
"Name=tag:kubernetes.io/role/internal-elb,Values=1" \
--query "Subnets[].SubnetId" --output text) )
echo "count=${#SUBNET_ARR[@]} subnets=${SUBNET_ARR[@]}"
aws rds create-db-subnet-group \
--db-subnet-group-name hub-subnets \
--db-subnet-group-description "hub private subnets" \
--subnet-ids "${SUBNET_ARR[@]}"
DB_SG=$(aws ec2 create-security-group --group-name hub-rds \
--description "RDS for hub" --vpc-id "$VPC_ID" \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$DB_SG" \
--protocol tcp --port 5432 --source-group "$NODE_SG"
aws rds create-db-instance \
--db-instance-identifier hub \
--db-instance-class db.t3.small \
--engine postgres --engine-version 17.6 \
--master-username hubadmin \
--master-user-password "$DB_PASSWORD" \
--allocated-storage 20 \
--db-subnet-group-name hub-subnets \
--vpc-security-group-ids "$DB_SG" \
--backup-retention-period 7 \
--storage-encrypted \
--no-publicly-accessible
aws rds wait db-instance-available --db-instance-identifier hub
DB_HOST=$(aws rds describe-db-instances --db-instance-identifier hub \
--query "DBInstances[0].Endpoint.Address" --output text)
echo "DB_HOST=$DB_HOST"
RDS PostgreSQL instance creation in private subnets, accessible only from the cluster's security group.
Step 3: Create the databases
Create the following three empty databases on the PostgreSQL instance from step 2 above. All three must be owned by the DB user referenced in the db-connection secret that will be created in step 7 below.
hub: application state for the Authorization Hub servicekeycloak: identity-provider state (realms, users, clients, sessions)domain_manager: ABAC policy storage for ADM
CREATE DATABASE hub;
CREATE DATABASE keycloak;
CREATE DATABASE domain_manager;
AWS example
kubectl run pgclient --rm -it --restart=Never --image=postgres:17 -- \
bash -c "PGPASSWORD='$DB_PASSWORD' psql -h $DB_HOST -U hubadmin -d postgres \
-c 'CREATE DATABASE hub;' \
-c 'CREATE DATABASE keycloak;' \
-c 'CREATE DATABASE domain_manager;'"
Temporary psql pod to create the databases. No bastion host or public database endpoint is required.
Step 4: Ingress controller
Deploy an ingress controller that meets the following requirements:
- Provisions a cloud L7 load balancer (public-facing or internal, based on your exposure model)
- Terminates TLS using the certificate from step 5 below
- Routes all external traffic to the
api-gatewayKubernetes service on port 80 - Forwards the
X-Forwarded-For,X-Forwarded-Proto, andX-Forwarded-Hostheaders (Keycloak relies on these to construct issuer URLs) - Supports HTTP health checks against
/actuator/health - Has permissions to create and modify load balancer resources in your cloud
Common options are the AWS Load Balancer Controller for EKS, GKE built-in Ingress or NGINX for GKE, and AGIC or NGINX Ingress Controller for AKS. For on-premises or portable deployments, use the NGINX Ingress Controller with cert-manager. When available, prefer the cloud-native controller as it integrates directly with the provider's certificate and DNS services.
AWS example
curl -fsSL -o /tmp/lbc-iam-policy.json \
"https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.4/docs/install/iam_policy.json"
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
POLICY_NAME=AWSLoadBalancerControllerIAMPolicy-${CLUSTER_NAME}
POLICY_ARN="arn:aws:iam::${ACCOUNT_ID}:policy/${POLICY_NAME}"
aws iam create-policy \
--policy-name "$POLICY_NAME" \
--policy-document file:///tmp/lbc-iam-policy.json
echo "POLICY_ARN=$POLICY_ARN"
eksctl create iamserviceaccount \
--cluster "$CLUSTER_NAME" --region "$AWS_REGION" \
--namespace kube-system --name aws-load-balancer-controller \
--attach-policy-arn "$POLICY_ARN" \
--override-existing-serviceaccounts --approve
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system --version 1.13.4 \
--set clusterName="$CLUSTER_NAME" \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller \
--set region="$AWS_REGION" \
--set vpcId="$VPC_ID"
kubectl -n kube-system rollout status deploy/aws-load-balancer-controller
# Ensure public subnets are tagged so the ALB controller can place the LB
for s in $(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \
--query "Subnets[?MapPublicIpOnLaunch==\`true\`].SubnetId" --output text); do
aws ec2 create-tags --resources "$s" --tags Key=kubernetes.io/role/elb,Value=1
done
AWS Load Balancer Controller installation using IRSA.
Step 5: TLS certificate
Obtain a TLS certificate for $HUB_HOST before proceeding. The certificate must:
- match the Hub hostname exactly (CN or SAN)
- be issued by a trusted public or internal CA
- support automated renewal as manual renewal is an operational risk for a long-lived service like Authorization Hub
Options for obtaining a certificate:
- Cloud-provider managed certificate: Renewal is automatic and no in-cluster management is required (AWS ACM, GCP Managed Certificates, Azure App Gateway certificates).
- cert-manager + Let's Encrypt: Portable across any cluster with HTTP-01 or DNS-01 validation. Use this when a cloud-managed option is not available.
AWS example
CERT_ARN=$(aws acm request-certificate \
--domain-name "$HUB_HOST" \
--validation-method DNS \
--region "$AWS_REGION" \
--query CertificateArn --output text)
echo "CERT_ARN=$CERT_ARN"
read -r NAME TYPE VALUE <<< $(aws acm describe-certificate --certificate-arn "$CERT_ARN" \
--query "Certificate.DomainValidationOptions[0].ResourceRecord.[Name,Type,Value]" --output text)
aws route53 change-resource-record-sets --hosted-zone-id "$HOSTED_ZONE_ID" \
--change-batch "{\"Changes\":[{\"Action\":\"UPSERT\",\"ResourceRecordSet\":{\"Name\":\"$NAME\",\"Type\":\"$TYPE\",\"TTL\":60,\"ResourceRecords\":[{\"Value\":\"$VALUE\"}]}}]}"
aws acm wait certificate-validated --certificate-arn "$CERT_ARN" --region "$AWS_REGION"
ACM certificate request and validation using a Route 53 DNS record.
ACM cannot issue certificates for AWS-owned hostnames (*.elb.amazonaws.com). You must own the domain.
Step 6: SMTP service (optional)
Configure an SMTP relay reachable from the cluster for Keycloak to use for password resets, user invitations, and email verification. This step is optional and you can deploy without SMTP, but email-driven flows will not work until it is configured.
Collect the following values before proceeding. They will be stored in the smtp secret in step 7 below.
- Host and port (commonly 587 for STARTTLS)
- Username and password (or API key equivalent)
- Sender address (typically
no-reply@yourdomain.com- must be a verified identity on the relay)
STARTTLS on port 587 is recommended. Plain port 25 is not accepted by most managed cluster outbound policies.
Common relay options are Amazon SES (AWS), SendGrid or Mailgun (GCP), and Azure Communication Services (Azure).
Step 7: Kubernetes secrets and license
Create the Kubernetes secrets in the install namespace and copy the license file into the chart's configurations/ directory.
For production environments, consider managing secrets through an external secret store (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) using the External Secrets Operator rather than raw kubectl create secret commands.
For the exact field names and commands, see the Deployment section above.
Step 8: Helm install
Install the Authorization Hub Helm chart, overriding the following values for your deployment:
registry: your container registry hostname and path, with a trailing slashhubHostname: the DNS name users will access (must match the TLS certificate)imagePullSecrets: list referencingregcredby nameingress.enabled: trueandingress.className: matching the ingress controller from step 4ingress.annotations: load balancer annotations (certificate reference, health-check path, SSL policy, and so on)ingress.hosts[0].host: same value ashubHostname
Use -f - to pass values through stdin to keep deployment-specific configuration out of the chart directory and avoid conflicts during future upgrades.
AWS example
cd src/main/helm
helm install hub \
-f hub/values.yaml \
-f - \
hub/ <<EOF
hubHostname: "${HUB_HOST}"
registry: "748131003707.dkr.ecr.eu-central-1.amazonaws.com/axiomatics/"
imagePullSecrets:
- name: regcred
ingress:
enabled: true
className: "alb"
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: "443"
alb.ingress.kubernetes.io/certificate-arn: "${CERT_ARN}"
alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true
alb.ingress.kubernetes.io/ssl-policy: "ELBSecurityPolicy-TLS13-1-2-2021-06"
alb.ingress.kubernetes.io/healthcheck-path: /actuator/health
alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
alb.ingress.kubernetes.io/backend-protocol: HTTP
alb.ingress.kubernetes.io/group.name: hub
hosts:
- host: "${HUB_HOST}"
paths:
- path: /
pathType: Prefix
tls: []
EOF
Chart installation with an ALB ingress, ACM certificate, and HTTPS-only listener with sticky sessions.
Step 9: DNS to load balancer
Create a DNS record for $HUB_HOST pointing to the load balancer provisioned in step 8. Retrieve the load balancer hostname from the Ingress status.
kubectl get ingress hub -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
AWS example
ALB=$(kubectl get ingress hub \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
echo "ALB=$ALB"
ALB_HZID=$(aws elbv2 describe-load-balancers \
--query "LoadBalancers[?DNSName=='$ALB'].CanonicalHostedZoneId" --output text)
aws route53 change-resource-record-sets --hosted-zone-id "$HOSTED_ZONE_ID" \
--change-batch "{\"Changes\":[{\"Action\":\"UPSERT\",\"ResourceRecordSet\":{\"Name\":\"$HUB_HOST\",\"Type\":\"A\",\"AliasTarget\":{\"HostedZoneId\":\"$ALB_HZID\",\"DNSName\":\"$ALB\",\"EvaluateTargetHealth\":false}}}]}"
A-ALIAS record creation in Route 53 pointing to the ALB.
Next step
Once the Authorization Hub is deployed, you should configure user authentication before you begin inviting users. Read the User authentication section for details.