Azure AKS Deployment
This is an example deployment of CogStack in Azure.
The recommended deployment of CogStack in Azure is based on using Kubernetes through Azure Kubernetes Service.
This example will create a AKS cluster, setup any necessary config, deploy CogStack to the cluster, and test that it is available. It will create publically accessible services, so is not suitable for production deployment.
We create a cluster following the Official Azure Verified Modules patterns in https://azure.github.io/Azure-Verified-Modules/indexes/terraform/tf-pattern-modules/ to create AKS clusters with their recommended defaults.
Usage
Deployment through terraform is carried out through two terraform commands, to handle the sequencing issues between making a k8s cluster and using it in the cloud.
Requirements
- Terraform - Install Terraform
- Azure Credentials for an account and subscription that can create and destroy resources.
Required Permissions
- Contributor
- User Access Administrator ...
Required Features
- EncryptionAtHost:
az feature register --namespace Microsoft.Compute --name EncryptionAtHost
1. Get the configuration files
All you need to do is get the Terraform files that have been preconfigured for this example (this download includes every deployment-examples tree; use the azure-kubernetes folder for this guide).
Download all deployment examples (ZIP)
Alternatively you can view the file contents here:
aks-cluster terraform files
This terraform configuration will create a new Azure AKS cluster.
module "aks" {
# Using the Azure Verified Module AKS Dev/Test module
# This is not recommended by Azure to be used for production deployments
# As of 26th 11 2025, the official module has a fatal bug on https://github.com/Azure/terraform-azurerm-avm-ptn-aks-dev/issues/55
# This error is fixed in this PR, switch back to the official module path when it's merged in
# https://github.com/Azure/terraform-azurerm-avm-ptn-aks-dev/pull/42
# For now use the fork for that PR:
source = "github.com/pauldotyu/terraform-azurerm-avm-ptn-aks-dev"
# source = "Azure/avm-ptn-aks-dev/azurerm"
# version = "0.2.0"
name = module.naming.kubernetes_cluster.name_unique
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
kubernetes_version = "1.35" # Set the k8s version to latest as of February 2026. This is to get the nodes running Ubuntu 24.04 and solve vm.max_map_count issue
}
# Alternative - use the Azure Verified Module AKS Production Module
# This is their recommended guidance for prod deployments. This requires more account permissions
#
# Datasource of current tenant ID
# data "azurerm_client_config" "current" {}
#
# module "avm-ptn-aks-production" {
# source = "Azure/avm-ptn-aks-production/azurerm"
# version = "0.5.0"
# location = azurerm_resource_group.this.location
# name = module.naming.kubernetes_cluster.name
# network = {
# node_subnet_id = module.avm_res_network_virtualnetwork.subnets["subnet"].resource_id
# pod_cidr = "192.168.0.0/16"
# service_cidr = "10.2.0.0/16"
# }
# resource_group_name = azurerm_resource_group.this.name
# rbac_aad_tenant_id = data.azurerm_client_config.current.tenant_id
# }
# module "avm_res_network_virtualnetwork" {
# source = "Azure/avm-res-network-virtualnetwork/azurerm"
# version = "0.7.1"
# address_space = ["10.31.0.0/16"]
# location = azurerm_resource_group.this.location
# name = module.naming.virtual_network.name
# resource_group_name = azurerm_resource_group.this.name
# subnets = {
# "subnet" = {
# name = "nodecidr"
# address_prefixes = ["10.31.0.0/17"]
# }
# "private_link_subnet" = {
# name = "private_link_subnet"
# address_prefixes = ["10.31.129.0/24"]
# }
# }
# }
module "naming" {
source = "Azure/naming/azurerm"
version = ">= 0.3.0"
prefix = ["cogstack", "terraform"]
}
data "azurerm_location" "this" {
location = "UK South"
}
resource "azurerm_resource_group" "this" {
name = module.naming.resource_group.name
location = data.azurerm_location.this.location
}
resource "local_file" "kubeconfig_file" {
content = module.aks.resource.kube_config_raw
filename = "${local.kubeconfig_file}"
file_permission = "0600"
}
output "resource_group_name" {
description = "The name of the resource group"
value = azurerm_resource_group.this.name
}
output "cluster_name" {
description = "The name of the EKS cluster"
value = module.aks.resource.name
sensitive = true
}
output "kubeconfig_file" {
value = abspath(local.kubeconfig_file)
description = "Path to the generated KUBECONFIG file used to connect to kubernetes"
}
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 4.0.0"
}
}
}
# Configure the Microsoft Azure Provider
provider "azurerm" {
features {}
}
locals {
kubeconfig_file = "${path.module}/.build/aks-kubeconfig.yaml"
created_cluster_name = module.aks.resource.name
}
kubernetes-deployment terraform files
This terraform configuration will use the helm provider to run services in kubernetes.
provider "helm" {
kubernetes = {
config_path = var.kubeconfig_file
}
}
provider "kubernetes" {
config_path = var.kubeconfig_file
}
# resource "kubernetes_ingress_class" "application_load_balancer" {
# # An Application Load Balancer is required in order to create public URLs.
# # The load balancer controller creates instances of the ALB when it sees ingress objects with the right annotations.
# metadata {
# name = "alb"
# labels = {
# "app.kubernetes.io/name" = "LoadBalancerController"
# }
# }
# spec {
# controller = "eks.amazonaws.com/alb"
# }
# }
locals {
services = {
medcat_service = {
path_prefix = "medcat-service"
}
}
}
module "cogstack_helm_services" {
# depends_on = [kubernetes_ingress_class.application_load_balancer]
source = "github.com/CogStack/cogstack-platform-toolkit//deployment/terraform/modules/cogstack-helm-services?ref=terraform-modules-v0.1.0"
# medcat_service_values = <<EOT
# ingress:
# annotations:
# alb.ingress.kubernetes.io/scheme: internet-facing
# alb.ingress.kubernetes.io/target-type: ip
# alb.ingress.kubernetes.io/healthcheck-path: /api/health/live
# className: alb
# http:
# - paths:
# - path: /${local.services.medcat_service.path_prefix}
# pathType: Prefix
# EOT
}
locals {
medcat_ingress_address = module.cogstack_helm_services.service_urls.medcat_service.ip_address
}
output "service_urls" {
value = {
medcat_service = {
example_curl = "curl http://${local.medcat_ingress_address}/${local.services.medcat_service.path_prefix}/api/info"
example_ui = "http://${local.medcat_ingress_address}/${local.services.medcat_service.path_prefix}/docs"
}
}
description = "Public URls to call services on"
}
variable "kubeconfig_file" {
description = "Path to the generated KUBECONFIG file used to connect to kubernetes"
}
2. Use the Azure CLI to login for your subscription
Run the az login command, which will open a web browser for you to login to your azure account. We then set the subscription ID for use by the Azure RM Terraform provider.
az login
export ARM_SUBSCRIPTION_ID=$(az account show --query id -o tsv)
3. Run Terraform
Terraform is run on two modules, so we will run one terraform apply in one folder, then another terraform apply in a second folder.
Initial provisioning takes around 15 minutes.
# Create AKS cluster
cd aks-cluster
terraform init
terraform apply --auto-approve
AZURE_KUBECONFIG=$(terraform output -raw kubeconfig_file)
# Deploy services to kubernetes
cd ../kubernetes-deployment
export TF_VAR_kubeconfig_file=$AZURE_KUBECONFIG
terraform init
terraform apply --auto-approve
4. Accessing the CogStack Platform
Once the deployment is complete and all services are running, you can access the CogStack platform and its components using the following URLs:
TODO: Create a public ingress url
# terraform output service_urls
kubectl port-forward deployment/medcat-service-terraform-medcat-service-helm 5000:5000
http://localhost:5000/demo
Optional - Destroy
You can destroy the infra to save costs when it wont be used for a long time.
cd ../kubernetes-deployment
terraform destroy
cd ../aks-cluster
terraform destroy
Optionally use the K8s cluster as normal with the CLI
After setting up the cluster, it is possible to interact directly with it using the kubectl CLI
The requirement is to get the KUBECONFIG file created by the terraform apply.
# Get KUBECONFIG
cd aks-cluster
AZURE_KUBECONFIG=$(terraform output -raw kubeconfig_file)
# SET KUBECONFIG
export KUBECONFIG=${AZURE_KUBECONFIG}
Note - alternatively you could use the Azure CLI to set your kubeconfig using
MY_RESOURCE_GROUP_NAME=$(terraform output -raw resource_group_name)
MY_AKS_CLUSTER_NAME=$(terraform output -raw cluster_name)
az aks get-credentials --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_AKS_CLUSTER_NAME`
You can then interact with kubernetes via the CLI for example:
# Run Medcat service
helm install my-medcat oci://registry-1.docker.io/cogstacksystems/medcat-service-helm --wait --timeout 10m0s
# Create the ingress
kubectl apply -f resources/ingress-medcat-service.yaml
# Find public url
kubectl get ingress