Migrating from cnp-module-redis to terraform-module-azure-managed-redis
Azure Managed Redis is a fully managed, enterprise-tier Redis service that replaces the
older Azure Cache for Redis (Standard/Premium) offering that cnp-module-redis provisions.
This guide explains the input/output differences and the steps needed to switch.
An agentic coding skill and example PRs are provided to assist adoption of the new module. This should be used to get the new infrastructure up and running in alongside your current Redis in sandbox (side by side).
Further information can be found by viewing the new module’s example folder, ‘inputs-*.tf’ files, the Azure documentation, terraform documentation or getting in touch with Platform Operations.
Quick navigation
- Feature parity
- Quick Start
- Data migration instructions
- Key differences at a glance
- Input mapping
- Output mapping
- SKU reference
- Fine-grained Migration steps
- Variables to add / remove from
variables.tf - Gotchas /Troubleshooting:
Feature parity
While MS is ending support for Az Cache for Redis, there is not complete feature parity between the offerings.
Please investigate & test these carefully as documentation from MS is hit or miss. MS Migration Docs
Quick Start
1) Build resources with Infrastructure as Code.
We have an agent skill which can assist you through the migration process. It will allow your agentic AI to assist and create Terraform and Flux PRs more easily. Check it out here! platops-agent-skills azure-managed-redis-migration .
Example PRs:
example folder, see example/main.tf in the new module’s repo
2) Run your infrastructure deployment pipeline for chosen environment
After deployment you should see:
- Creation for new Redis infrastructure, with associated networking and secrets accordingly.
No destroys should've occurred unless you are intentionally planning to replace (destroy forever) your current instance and secrets. If any destroys are planned and you're not sure, please get in touch as this is likely to be an _irreversible_ change.
- New secret should be added to your keyvault
OPTIONAL) You may choose to spin up a kubernetes pod to test your redis instance is available. Unsupported documentation is here: [Platops confluence](https://tools.hmcts.net/confluence/spaces/DTSPO/pages/1973497319/Plum+Managed+Redis+%E2%80%94+Connection+Validation+Guide). Alternatively continue below if you're already comfortable writing/reading keys from redis.
3) Update application/ code to target the new resource
Ensure you create the redis connection string as a new Keyvault secret resource. That way, if rollback is needed, you can point back to your preexisting secret via a flux PR.
Please see this reference PR for how to switch connection string using flux config. It uses a new Key Vault secret but same variable name for the application.
Data migration instructions
If data migration is required, please follow the instructions here
Key differences at a glance
| Concern | cnp-module-redis |
terraform-module-azure-managed-redis |
|---|---|---|
| Redis tier (See Azure docs here ) | Basic / Standard / Premium | Performant SKUs are still very expensive! Balanced / ComputeOptimized / MemoryOptimized / FlashOptimized |
| SKU format (SKU Reference table below) |
sku_name + family + capacity
|
Single sku_name e.g. "Balanced_B1"
|
| Redis version |
redis_version (4 / 6) |
Always ‘Current’ (parameter removed) |
| Authentication | Access key by default | Entra ID / RBAC by default; keys opt-in |
| Private endpoint flag |
private_endpoint_enabled (bool) |
create_private_endpoint (bool) |
| Public access flag |
public_network_access_enabled (bool) |
public_network_access ("Enabled" / "Disabled") |
| RDB backup frequency | Integer minutes (60, 360, 720) |
Duration strings ("1h", "6h", "12h") |
| Backup storage | rdb_storage_account_name_prefix |
Managed internally — parameter no longer needed |
| Resource group | Shared / caller-provided | Module creates its own RG by default |
| Private DNS zone | privatelink.redis.cache.windows.net |
privatelink.redis.azure.net |
Input mapping
Removed inputs (no equivalent needed)
| Old input | Reason |
|---|---|
redis_version |
Managed Redis version is managed by Azure |
family |
Encoded in sku_name
|
capacity |
Encoded in sku_name
|
business_area |
No longer used by this module |
rdb_backup_enabled |
Backup is enabled by setting persistence_rdb_backup_frequency
|
rdb_backup_max_snapshot_count |
Var not supported by Managed Redis |
rdb_storage_account_name_prefix |
Managed internally |
Changed inputs
| Old input | New input | Notes |
|---|---|---|
private_endpoint_enabled = true |
create_private_endpoint = true |
Also supply subnet_id and private_dns_zone_ids
|
public_network_access_enabled = false |
public_network_access = "Disabled" |
Accepts "Enabled" or "Disabled"
|
rdb_backup_frequency = 360 |
persistence_rdb_backup_frequency = "6h" |
Values: "1h", "6h", "12h"
|
sku_name = "Premium" + family = "P" + capacity = 1
|
sku_name = "Balanced_B3" |
See SKU reference below |
New required inputs
| Input | Description |
|---|---|
component |
HMCTS component name — used to form the resource name |
subnet_id |
Subnet for the private endpoint (required when create_private_endpoint = true) |
private_dns_zone_ids |
List of private DNS zone IDs for the private endpoint |
Authentication
cnp-module-redis always issues access keys. The new module defaults to Entra ID / RBAC
only. To keep using access keys (e.g. for connection strings), set:
access_keys_authentication_enabled = true
Output mapping
| Old output | New output | Backward-compatible alias |
|---|---|---|
access_key |
primary_access_key |
redis_cache_primary_access_key |
host_name |
hostname |
redis_cache_hostname |
redis_port |
port |
redis_cache_ssl_port |
The backward-compatible aliases allow you to rename the module block without updating every downstream reference at once.
SKU reference
OLD Azure Cache for Redis - pricing
Managed Redis SKUs combine tier and size into one string:
| Tier | Example SKUs | Rough equivalent |
|---|---|---|
Balanced (B) |
Balanced_B1, Balanced_B3, Balanced_B5
|
General purpose workloads |
MemoryOptimized (M) |
MemoryOptimized_M10, M20
|
Large datasets, low throughput |
ComputeOptimized (X) |
ComputeOptimized_X3, X5
|
High-throughput, low-latency |
FlashOptimized (A) |
FlashOptimized_A250 |
Very large datasets with NVMe |
If you were using Premium P1 (family = "P", capacity = 1), start with Balanced_B3
and adjust based on memory and throughput requirements.
Fine-grained Migration steps
1. Add the new module block alongside the old one
Keep reference to old module cnp-module-redis in place during the transition. Add the new module with
a for_each guard to deploy only to non-production environments first:
module "managed_redis" {
for_each = toset(contains(["sandbox", "aat"], var.env) ? [var.env] : [])
source = "git@github.com:hmcts/terraform-module-azure-managed-redis?ref=main"
product = var.product
component = var.component
env = var.env
location = var.location
common_tags = var.common_tags
public_network_access = "Disabled"
create_private_endpoint = true
subnet_id = data.azurerm_subnet.redis_private_endpoint.id
private_dns_zone_ids = [
"/subscriptions/${var.private_dns_subscription_id}/resourceGroups/core-infra-intsvc-rg/providers/Microsoft.Network/privateDnsZones/privatelink.redis.azure.net"
]
access_keys_authentication_enabled = true
persistence_rdb_backup_frequency = "6h"
}
Note the DNS zone hostname has changed from
privatelink.redis.cache.windows.net → privatelink.redis.azure.net.
2. Update connection string references
Replace any outputs consumed from the old module:
# Before
value = "redis://ignore:${urlencode(module.plum-redis-storage.access_key)}@${module.plum-redis-storage.host_name}:${module.plum-redis-storage.redis_port}?tls=true"
# After (using for_each — pick the single instance)
value = "redis://ignore:${urlencode(one(values(module.managed_redis)).primary_access_key)}@${one(values(module.managed_redis)).hostname}:${one(values(module.managed_redis)).port}?tls=true"
If you are not using for_each, the reference is simpler:
value = "redis://ignore:${urlencode(module.managed_redis.primary_access_key)}@${module.managed_redis.hostname}:${module.managed_redis.port}?tls=true"
3. Update Key Vault secrets
Any azurerm_key_vault_secret resources that read from the old module outputs must be
updated to reference the new module. Run terraform plan and verify only the secret
value changes, not a destroy/recreate of the secret resource itself.
4. Remove the old module block
Once the new Redis instance is confirmed healthy and applications are pointing at it,
remove the module "plum-redis-storage" block and run terraform apply. Terraform will
destroy the old azurerm_redis_cache resource.
5. Remove the for_each guard
After a successful rollout to lower environments, extend the for_each to include
production (or remove the guard entirely):
# Remove for_each entirely for a simple single-instance deployment
module "managed_redis" {
source = "git@github.com:hmcts/terraform-module-azure-managed-redis?ref=main"
...
}
As mentioned in the example PR, removing this may cause your resources within terraform’s state to change slightly. Please see the PR for remediation steps
Variables to add / remove from variables.tf
Remove (check if these are actually only utilised by cnp-module-redis):
variable "sku_name" {}
variable "family" {}
variable "redis_capacity" {}
variable "rdb_backup_enabled" {}
variable "redis_backup_frequency" {}
variable "rdb_backup_max_snapshot_count" {}
The new module reuses product, component, env, location, and common_tags which
are already standard variables in HMCTS Terraform configurations.
Gotchas /Troubleshooting:
Access keys: If you utilise the same terraform secret resource as your current redis infrastructure, the ‘current/old’ secrets will be visible as old versions on the keyvault
Azure keyvault encodes keys with base64 encoding so you may see %3D (=) padding
Clustering (-c flag) Azure Managed Redis runs as a clustered Redis Enterprise instance with two shards (slots 0-8191 and 8192-16383). When you connect with redis-cli, your session lands on one shard. When initiating a connection,remember to connect to the cluster rather than a single shard. Commands against keys owned by the other shard return nothing or a MOVED redirect.
Database number Azure Managed Redis supports only a single database (database 0). If your application uses multiple databases, you need to refactor your data model to use a single database or use key prefixes to logically separate data before migrating.