From 90263e2c46109216aeca830b0c3abe291c4ecd5d Mon Sep 17 00:00:00 2001 From: Mangesh Sangapu Date: Wed, 22 Jul 2026 20:06:53 +0000 Subject: [PATCH 1/5] Azure Managed Redis updates --- .env | 2 +- README.md | 2 +- infra/resources.bicep | 25 +++++++++++++++---------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.env b/.env index 7e893174c..70e264891 100644 --- a/.env +++ b/.env @@ -2,5 +2,5 @@ DBNAME=app DBHOST=localhost DBUSER=app_user DBPASS=app_password -CACHELOCATION=redis://redis:6379/0 +CACHELOCATION=redis://redis:10000/0 SECRET_KEY=secret_key diff --git a/README.md b/README.md index 260d41e42..b6b36ec52 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ description: This is a Python web app using the Django framework and the Azure D This is a Python web app using the Django framework and the Azure Database for PostgreSQL relational database service. The Django app is hosted in a fully managed Azure App Service. This app is designed to be be run locally and then deployed to Azure. You can either deploy this project by following the tutorial [*Deploy a Python (Django or Flask) web app with PostgreSQL in Azure*](https://docs.microsoft.com/azure/app-service/tutorial-python-postgresql-app) or by using the [Azure Developer CLI (azd)](https://learn.microsoft.com/azure/developer/azure-developer-cli/overview) according to the instructions below. -Additionally, the sample application demonstrates Azure Redis Cache access by caching the restaurant details page for 60 seconds. You can add the Azure Redis Cache integration in the [secure-by-default web app + database creation wizard](https://portal.azure.com/?feature.customportal=false#create/Microsoft.AppServiceWebAppDatabaseV3), and it's also included in the [AZD template](https://github.com/Azure-Samples/python-app-service-postgresql-infra). +Additionally, the sample application demonstrates Azure Managed Redis access by caching the restaurant details page for 60 seconds. You can add the Azure Managed Redis integration in the [secure-by-default web app + database creation wizard](https://portal.azure.com/?feature.customportal=false#create/Microsoft.AppServiceWebAppDatabaseV3), and it's also included in the [AZD template](https://github.com/Azure-Samples/python-app-service-postgresql-infra). ## Requirements diff --git a/infra/resources.bicep b/infra/resources.bicep index f5fba55f0..8b0ab66d5 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -275,20 +275,25 @@ resource dbserver 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' } // The Redis cache is configured to the minimum pricing tier -resource redisCache 'Microsoft.Cache/redis@2024-11-01' = { +resource redisCache 'Microsoft.Cache/redisEnterprise@2026-05-01-preview' = { name: '${appName}-cache' location: location + sku: { + name: 'Balanced_B0' + } properties: { - sku: { - name: 'Basic' - family: 'C' - capacity: 0 - } - redisConfiguration: {} - enableNonSslPort: false - redisVersion: '6' + minimumTlsVersion: '1.2' publicNetworkAccess: 'Disabled' } + + // Azure Managed Redis authentication + resource redisDatabase 'databases@2026-05-01-preview' = { + name: 'default' + properties: { + accessKeysAuthentication: 'Enabled' + } + } + } // The App Service plan is configured to the B1 pricing tier @@ -430,7 +435,7 @@ resource cacheConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { clientType: 'python' targetService: { type: 'AzureResource' - id: resourceId('Microsoft.Cache/Redis/Databases', redisCache.name, '0') + id: redisCache::redisDatabase.id } authInfo: { authType: 'accessKey' From 7be41aae338a786f041b01992585fed005ec8237 Mon Sep 17 00:00:00 2001 From: Mangesh Sangapu Date: Thu, 23 Jul 2026 04:07:53 +0000 Subject: [PATCH 2/5] Align sample with Azure Managed Redis Enterprise Update infrastructure to use Azure Managed Redis Enterprise conventions. Switch private endpoint group to redisEnterprise and DNS zone to privatelink.redis.azure.net. Add Redis Enterprise encryption block and update linker target to database resource ID. Refresh README wording to remove legacy Redis Cache phrasing. --- README.md | 2 +- infra/resources.bicep | 24 +++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b6b36ec52..f523972fa 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The [requirements.txt](./requirements.txt) has the following packages, all used | [pyscopg2-binary](https://pypi.org/project/psycopg-binary/) | PostgreSQL database adapter for Python. | | [python-dotenv](https://pypi.org/project/python-dotenv/) | Read key-value pairs from .env file and set them as environment variables. In this sample app, those variables describe how to connect to the database locally.

This package is used in the [manage.py](./manage.py) file to load environment variables. | | [whitenoise](https://pypi.org/project/whitenoise/) | Static file serving for WSGI applications, used in the deployed app.

This package is used in the [azureproject/production.py](./azureproject/production.py) file, which configures production settings. | -| [django-redis](https://pypi.org/project/django-redis/) | Redis cache backend for Django. | +| [django-redis](https://pypi.org/project/django-redis/) | Django integration for Azure Managed Redis and Redis-compatible caching backends. | ## Run the sample diff --git a/infra/resources.bicep b/infra/resources.bicep index 8b0ab66d5..438ae2d69 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -148,7 +148,7 @@ resource privateDnsZoneDB 'Microsoft.Network/privateDnsZones@2024-06-01' = { } } -// Resources needed to secure Redis Cache behind a private endpoint +// Resources needed to secure Azure Managed Redis behind a private endpoint resource cachePrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-03-01' = { name: '${appName}-cache-privateEndpoint' location: location @@ -161,7 +161,7 @@ resource cachePrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-03-01' = name: '${appName}-cache-privateEndpoint' properties: { privateLinkServiceId: redisCache.id - groupIds: ['redisCache'] + groupIds: ['redisEnterprise'] } } ] @@ -181,7 +181,7 @@ resource cachePrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-03-01' = } } resource privateDnsZoneCache 'Microsoft.Network/privateDnsZones@2024-06-01' = { - name: 'privatelink.redis.cache.windows.net' + name: 'privatelink.redis.azure.net' location: 'global' dependsOn: [ virtualNetwork @@ -274,7 +274,7 @@ resource dbserver 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' ] } -// The Redis cache is configured to the minimum pricing tier +// Azure Managed Redis is configured to the minimum pricing tier resource redisCache 'Microsoft.Cache/redisEnterprise@2026-05-01-preview' = { name: '${appName}-cache' location: location @@ -282,18 +282,16 @@ resource redisCache 'Microsoft.Cache/redisEnterprise@2026-05-01-preview' = { name: 'Balanced_B0' } properties: { + encryption: {} minimumTlsVersion: '1.2' publicNetworkAccess: 'Disabled' } +} - // Azure Managed Redis authentication - resource redisDatabase 'databases@2026-05-01-preview' = { - name: 'default' - properties: { - accessKeysAuthentication: 'Enabled' - } - } - +// The default Redis Enterprise database is platform-managed; reference it by ID. +resource redisDatabase 'Microsoft.Cache/redisEnterprise/databases@2026-05-01-preview' existing = { + parent: redisCache + name: 'default' } // The App Service plan is configured to the B1 pricing tier @@ -435,7 +433,7 @@ resource cacheConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { clientType: 'python' targetService: { type: 'AzureResource' - id: redisCache::redisDatabase.id + id: redisDatabase.id } authInfo: { authType: 'accessKey' From 4315f59c4d894780573bf5c7aef30558fc20a47b Mon Sep 17 00:00:00 2001 From: Mangesh Sangapu Date: Wed, 22 Jul 2026 23:58:39 -0500 Subject: [PATCH 3/5] Refactor docker-compose.yml for service dependencies Updated PostgreSQL and Redis images to specific versions and added health checks for the database service. --- .devcontainer/docker-compose.yml | 38 ++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 218553667..c00e9e7c6 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3" - services: app: build: @@ -9,28 +7,50 @@ services: volumes: - ..:/workspace:cached - # Overrides default command so things don't shut down after the process ends. + # Keep the development container running. command: sleep infinity - # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. - network_mode: service:db + # Start supporting services before the app container. + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + + networks: + - app-network db: - image: postgres:latest + image: postgres:17 restart: unless-stopped + volumes: - postgres-data:/var/lib/postgresql/data + environment: POSTGRES_DB: app POSTGRES_USER: app_user POSTGRES_PASSWORD: app_password - # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) + healthcheck: + test: ["CMD-SHELL", "pg_isready -U app_user -d app"] + interval: 5s + timeout: 5s + retries: 10 + + networks: + - app-network redis: - image: redis + image: redis:7-alpine restart: unless-stopped + networks: + - app-network + volumes: postgres-data: + +networks: + app-network: + driver: bridge From e8aad2a0f8656de7563c24affb4510704b098fc0 Mon Sep 17 00:00:00 2001 From: Mangesh Sangapu Date: Thu, 23 Jul 2026 09:18:04 -0500 Subject: [PATCH 4/5] Keep minimal devcontainer improvements and restore original app networking --- .devcontainer/docker-compose.yml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index c00e9e7c6..b05ce65ee 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -17,8 +17,7 @@ services: redis: condition: service_started - networks: - - app-network + network_mode: service:db db: image: postgres:17 @@ -38,19 +37,9 @@ services: timeout: 5s retries: 10 - networks: - - app-network - redis: image: redis:7-alpine restart: unless-stopped - networks: - - app-network - volumes: postgres-data: - -networks: - app-network: - driver: bridge From 8f4e3c6174734b96de95d256ac48a6125011265a Mon Sep 17 00:00:00 2001 From: Mangesh Sangapu Date: Fri, 24 Jul 2026 14:18:37 -0500 Subject: [PATCH 5/5] Apply Azure resource API version updates and Redis database fix from Flask sample PR --- infra/resources.bicep | 56 +++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/infra/resources.bicep b/infra/resources.bicep index 438ae2d69..e49e9f5e3 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -83,10 +83,10 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-01-01' = { } // Resources needed to secure Key Vault behind a private endpoint -resource privateDnsZoneKeyVault 'Microsoft.Network/privateDnsZones@2020-06-01' = { +resource privateDnsZoneKeyVault 'Microsoft.Network/privateDnsZones@2024-06-01' = { name: 'privatelink.vaultcore.azure.net' location: 'global' - resource vnetLink 'virtualNetworkLinks@2020-06-01' = { + resource vnetLink 'virtualNetworkLinks@2024-06-01' = { location: 'global' name: '${appName}-vaultlink' properties: { @@ -97,7 +97,7 @@ resource privateDnsZoneKeyVault 'Microsoft.Network/privateDnsZones@2020-06-01' = } } } -resource vaultPrivateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = { +resource vaultPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-01-01' = { name: '${appName}-vault-privateEndpoint' location: location properties: { @@ -149,7 +149,7 @@ resource privateDnsZoneDB 'Microsoft.Network/privateDnsZones@2024-06-01' = { } // Resources needed to secure Azure Managed Redis behind a private endpoint -resource cachePrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-03-01' = { +resource cachePrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-01-01' = { name: '${appName}-cache-privateEndpoint' location: location properties: { @@ -186,7 +186,7 @@ resource privateDnsZoneCache 'Microsoft.Network/privateDnsZones@2024-06-01' = { dependsOn: [ virtualNetwork ] - resource privateDnsZoneLinkCache 'virtualNetworkLinks@2020-06-01' = { + resource privateDnsZoneLinkCache 'virtualNetworkLinks@2024-06-01' = { name: '${appName}-cachelink' location: 'global' properties: { @@ -200,7 +200,7 @@ resource privateDnsZoneCache 'Microsoft.Network/privateDnsZones@2024-06-01' = { // The Key Vault is used to manage SQL database and redis secrets. // Current user has the admin permissions to configure key vault secrets, but by default doesn't have the permissions to read them. -resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { +resource keyVault 'Microsoft.KeyVault/vaults@2026-03-01-preview' = { name: '${take(replace(appName, '-', ''), 17)}-vault' location: location properties: { @@ -218,11 +218,11 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { // Grant the current user with key vault secret user role permissions over the key vault. This lets you inspect the secrets, such as in the portal // If you remove this section, you can't read the key vault secrets, but the app still has access with its managed identity. -resource keyVaultSecretUserRoleRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { +resource keyVaultSecretUserRoleRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-04-preview' existing = { scope: subscription() name: '4633458b-17de-408a-b874-0445c86b69e6' // The built-in Key Vault Secret User role } -resource keyVaultSecretUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = { +resource keyVaultSecretUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { scope: keyVault name: guid(resourceGroup().id, principalId, keyVaultSecretUserRoleRoleDefinition.id) properties: { @@ -232,7 +232,7 @@ resource keyVaultSecretUserRoleAssignment 'Microsoft.Authorization/roleAssignmen } } -resource dbserver 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' = { +resource dbserver 'Microsoft.DBforPostgreSQL/flexibleServers@2026-04-01-preview' = { location: location tags: tags name: pgServerName @@ -288,14 +288,20 @@ resource redisCache 'Microsoft.Cache/redisEnterprise@2026-05-01-preview' = { } } -// The default Redis Enterprise database is platform-managed; reference it by ID. -resource redisDatabase 'Microsoft.Cache/redisEnterprise/databases@2026-05-01-preview' existing = { +resource redisDatabase 'Microsoft.Cache/redisEnterprise/databases@2026-05-01-preview' = { parent: redisCache name: 'default' + properties: { + accessKeysAuthentication: 'Enabled' + clientProtocol: 'Encrypted' + clusteringPolicy: 'OSSCluster' + evictionPolicy: 'VolatileLRU' + port: 10000 + } } // The App Service plan is configured to the B1 pricing tier -resource appServicePlan 'Microsoft.Web/serverfarms@2024-04-01' = { +resource appServicePlan 'Microsoft.Web/serverfarms@2025-03-01' = { name: '${appName}-plan' location: location kind: 'linux' @@ -307,7 +313,7 @@ resource appServicePlan 'Microsoft.Web/serverfarms@2024-04-01' = { } } -resource web 'Microsoft.Web/sites@2024-04-01' = { +resource web 'Microsoft.Web/sites@2025-03-01' = { name: appName location: location tags: union(tags, { 'azd-service-name': 'web' }) // Needed by AZD @@ -328,13 +334,13 @@ resource web 'Microsoft.Web/sites@2024-04-01' = { // For app setting configuration see the appsettings resource // Disable basic authentication for FTP and SCM - resource ftp 'basicPublishingCredentialsPolicies@2023-12-01' = { + resource ftp 'basicPublishingCredentialsPolicies@2025-03-01' = { name: 'ftp' properties: { allow: false } } - resource scm 'basicPublishingCredentialsPolicies@2023-12-01' = { + resource scm 'basicPublishingCredentialsPolicies@2025-03-01' = { name: 'scm' properties: { allow: false @@ -380,7 +386,7 @@ resource web 'Microsoft.Web/sites@2024-04-01' = { // Service Connector from the app to the key vault, which generates the connection settings for the App Service app // The application code doesn't make any direct connections to the key vault, but the setup expedites the managed identity access // so that the cache connector can be configured with key vault references. -resource vaultConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { +resource vaultConnector 'Microsoft.ServiceLinker/linkers@2024-07-01-preview' = { scope: web name: 'vaultConnector' properties: { @@ -402,7 +408,7 @@ resource vaultConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { } // Connector to the PostgreSQL database, which generates the connection string for the App Service app -resource dbConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { +resource dbConnector 'Microsoft.ServiceLinker/linkers@2024-07-01-preview' = { scope: web name: 'defaultConnector' properties: { @@ -426,7 +432,7 @@ resource dbConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { } // Service Connector from the app to the cache, which generates an app setting for the App Service app -resource cacheConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { +resource cacheConnector 'Microsoft.ServiceLinker/linkers@2024-07-01-preview' = { scope: web name: 'RedisConnector' properties: { @@ -491,7 +497,7 @@ resource webdiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-previe } } -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2025-07-01' = { name: '${appName}-workspace' location: location tags: tags @@ -531,7 +537,7 @@ var aggregatedAppSettings = union( // 'FOO': 'BAR' } ) -resource appsettings 'Microsoft.Web/sites/config@2024-04-01' = { +resource appsettings 'Microsoft.Web/sites/config@2025-03-01' = { name: 'appsettings' parent: web properties: aggregatedAppSettings @@ -543,7 +549,15 @@ resource appsettings 'Microsoft.Web/sites/config@2024-04-01' = { // is a workaround to ensure that the app settings are aggregated correctly and consistent across multiple deployments. output WEB_URI string = 'https://${web.properties.defaultHostName}' -output CONNECTION_SETTINGS array = map(concat(dbConnector.listConfigurations().configurations, cacheConnector.listConfigurations().configurations, vaultConnector.listConfigurations().configurations), config => config.name) +output CONNECTION_SETTINGS array = [ + 'AZURE_POSTGRESQL_NAME' + 'AZURE_POSTGRESQL_HOST' + 'AZURE_POSTGRESQL_USER' + 'AZURE_POSTGRESQL_PASSWORD' + 'AZURE_REDIS_CONNECTIONSTRING' + 'AZURE_KEYVAULT_RESOURCEENDPOINT' + 'AZURE_KEYVAULT_SCOPE' +] output APPLICATIONINSIGHTS_CONNECTION_STRING string = applicationInsightsResources.outputs.APPLICATIONINSIGHTS_CONNECTION_STRING output WEB_APP_LOG_STREAM string = format('https://portal.azure.com/#@/resource{0}/logStream', web.id) output WEB_APP_SSH string = format('https://{0}.scm.azurewebsites.net/webssh/host', web.name)