Terraform for Healthcare Infrastructure: HIPAA, GxP, and Zero-Trust on Azure
Building cloud infrastructure for clinical trials is not like building a SaaS product. Here is how I designed 12 Terraform modules for HIPAA/GxP compliance with zero-trust architecture, and the specific patterns that make auditors happy.
Alexis Morin
Senior DevOps & Go Engineer
8 March 2026
9 min read
Why Healthcare Infrastructure Is Different
When I designed the Azure infrastructure for a clinical trials platform, I quickly learned that healthcare cloud is a different category from standard SaaS infrastructure. The stakes are different: HIPAA violations can mean fines in the millions and criminal liability. GxP (Good Practice) regulations in clinical trials require full audit trails, validated systems, and documented change control.
This is not about paranoia. It is about building systems where compliance is structural, where the infrastructure itself makes non-compliant configurations impossible, not just discouraged.
The Architecture: 3 Isolated Environments
The foundation was three completely isolated environments, each with its own VNet, its own resource groups, its own access controls. Not "separated by IAM policies" isolated. Physically separated networks.
# Each environment gets a dedicated VNet with non-overlapping CIDRs
module "vnet_dev" {
source = "./modules/vnet"
name = "vnet-clinical-dev"
address_space = ["10.1.0.0/16"]
environment = "dev"
}
module "vnet_staging" {
source = "./modules/vnet"
name = "vnet-clinical-staging"
address_space = ["10.2.0.0/16"]
environment = "staging"
}
module "vnet_prod" {
source = "./modules/vnet"
name = "vnet-clinical-prod"
address_space = ["10.3.0.0/16"]
environment = "prod"
}No peering between prod and dev. No shared resources. If dev is compromised, prod is untouched.
Zero-Trust: Private Endpoints for Everything
In standard cloud architectures, services communicate over the public internet with TLS and API keys. In zero-trust healthcare infrastructure, services communicate exclusively over private endpoints with no public internet exposure.
# Every Azure service gets a private endpoint, no public access
resource "azurerm_private_endpoint" "keyvault" {
name = "pe-kv-${var.environment}"
location = var.location
resource_group_name = var.resource_group_name
subnet_id = var.private_endpoint_subnet_id
private_service_connection {
name = "psc-kv-${var.environment}"
private_connection_resource_id = azurerm_key_vault.main.id
subresource_names = ["vault"]
is_manual_connection = false
}
}
# Disable public network access, enforce private only
resource "azurerm_key_vault" "main" {
name = "kv-clinical-${var.environment}"
public_network_access_enabled = false
# ...
}This applies to Key Vault, Storage, SQL, AKS API server, Container Registry. Everything. External access is only possible through Azure Bastion (browser-based, no open SSH ports).
Private AKS: No Public API Server
The AKS cluster API server is private. This means kubectl commands only work from within the VNet or over VPN. No exposed Kubernetes API endpoint on the internet.
resource "azurerm_kubernetes_cluster" "main" {
name = "aks-clinical-${var.environment}"
private_cluster_enabled = true
network_profile {
network_plugin = "azure"
network_policy = "azure"
load_balancer_sku = "standard"
outbound_type = "userDefinedRouting"
}
# No public node pools
default_node_pool {
enable_node_public_ip = false
vnet_subnet_id = var.aks_subnet_id
}
}Combined with Azure Bastion for operator access, there is literally no public attack surface on the cluster.
Audit Trails: What Auditors Actually Check
HIPAA and GxP auditors will look for:
1. Who changed what, when: Azure Activity Log + Log Analytics Workspace, retained for minimum 7 years
2. Encryption at rest and in transit: enforced via Azure Policy (deny non-compliant resources)
3. Access reviews: all human access is time-limited via Privileged Identity Management (PIM)
4. Change management: every Terraform change goes through a pull request with required approvals
# Enforce encryption at rest via Azure Policy
resource "azurerm_policy_assignment" "require_encryption" {
name = "require-encryption-${var.environment}"
scope = data.azurerm_resource_group.main.id
policy_definition_id = "/providers/Microsoft.Authorization/policyDefinitions/..."
enforcement_mode = "Default"
}The Log Analytics workspace is write-only for services. No service can delete its own logs. Retention is set to 2,557 days (7 years).
The 12 Module Structure
Breaking the infrastructure into focused modules made validation and change control tractable:
- •vnet | networking, subnets, NSGs, route tables
- •bastion | Azure Bastion for operator access
- •aks | private cluster, node pools, RBAC
- •keyvault | secrets management, access policies
- •storage | blob storage with private endpoints
- •sql | Azure SQL with private endpoint, encryption
- •acr | Container Registry, private endpoint
- •monitoring | Log Analytics, alerts, dashboards
- •policy | Azure Policy assignments for compliance
- •identity | Managed Identities, PIM configuration
- •backup | backup policies for databases and storage
- •dns | private DNS zones for all private endpoints
Each module has its own variables.tf, outputs.tf, and README.md. The README is not optional. It is part of GxP documentation.
The Result
Zero security incidents across three environments over the project lifetime. The infrastructure passed the compliance review on the first audit with no findings and no remediation items. That is what structural compliance looks like: the architecture itself makes violations impossible rather than just unlikely.
If you are building healthcare infrastructure and want a review of your current setup, reach out.
FREE CONSULTATION
Want to reduce your cloud costs?
I offer a free 15-minute infrastructure audit. I'll show you exactly where your cloud budget is going and what to fix first.
Book Free Audit