@@ -141,6 +141,10 @@ param backendImageName string = 'content-gen-api'
141141@description ('Optional. Image tag for container deployment. Leave empty to skip ACI deployment.' )
142142param imageTag string
143143
144+ @description ('Optional. Azure Container Registry name (unused - ACR name is auto-generated). Declared for parameter file compatibility.' )
145+ #disable-next-line no-unused-params
146+ param acrName string = ''
147+
144148@description ('Optional. Created by user name.' )
145149param createdBy string = contains (deployer (), 'userPrincipalName' )? split (deployer ().userPrincipalName , '@' )[0 ]: deployer ().objectId
146150
@@ -160,6 +164,7 @@ var solutionSuffix = toLower(trim(replace(
160164 ''
161165)))
162166
167+ // ACR name is always auto-generated in custom deployment
163168var acrResourceName = 'cr${solutionSuffix }'
164169
165170var cosmosDbZoneRedundantHaRegionPairs = {
@@ -381,9 +386,16 @@ module containerRegistry 'br/public:avm/res/container-registry/registry:0.9.0' =
381386 enableTelemetry : enableTelemetry
382387 acrSku : 'Standard'
383388 acrAdminUserEnabled : false
384- anonymousPullEnabled : true // Allows ACI to pull images without credentials
389+ anonymousPullEnabled : false
385390 publicNetworkAccess : 'Enabled'
386391 networkRuleBypassOptions : 'AzureServices'
392+ roleAssignments : [
393+ {
394+ principalId : userAssignedIdentity .outputs .principalId
395+ roleDefinitionIdOrName : '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull
396+ principalType : 'ServicePrincipal'
397+ }
398+ ]
387399 }
388400}
389401
@@ -889,61 +901,120 @@ module webSite 'modules/web-sites.bicep' = {
889901}
890902
891903// ========== Container Instance (Backend API) ========== //
892- // CUSTOM DEPLOYMENT: ACI is skipped when imageTag='none' (first run), deployed after images are built
904+ // CUSTOM DEPLOYMENT: Inline ACI definition with managed identity auth for ACR
893905var containerInstanceName = 'aci-${solutionSuffix }'
894906var backendImageUrl = '${containerRegistry .outputs .loginServer }/${backendImageName }:${imageTag }'
907+ var aciPort = 8000
908+ var isPrivateNetworking = enablePrivateNetworking
909+ // Construct identity resource ID from known values (required for deployment-time calculation)
910+ var userAssignedIdentityResourceIdForACI = '/subscriptions/${subscription ().subscriptionId }/resourceGroups/${resourceGroup ().name }/providers/Microsoft.ManagedIdentity/userAssignedIdentities/${userAssignedIdentityResourceName }'
895911// Deploy ACI only when imageTag is set to a real tag (not 'none')
896912var shouldDeployACI = !empty (imageTag ) && imageTag != 'none'
897- module containerInstance 'modules/container-instance.bicep' = if (shouldDeployACI ) {
898- name : take ('module.container-instance.${containerInstanceName }' , 64 )
899- params : {
900- name : containerInstanceName
901- location : solutionLocation
902- tags : tags
903- containerImage : backendImageUrl
904- cpu : 2
905- memoryInGB : 4
906- port : 8000
907- // Only pass subnetResourceId when private networking is enabled
908- subnetResourceId : enablePrivateNetworking ? virtualNetwork !.outputs .aciSubnetResourceId : ''
909- userAssignedIdentityResourceId : userAssignedIdentity .outputs .resourceId
910- enableTelemetry : enableTelemetry
911- environmentVariables : [
912- // Azure OpenAI Settings
913- { name : 'AZURE_OPENAI_ENDPOINT' , value : 'https://${aiFoundryAiServicesResourceName }.openai.azure.com/' }
914- { name : 'AZURE_OPENAI_GPT_MODEL' , value : gptModelName }
915- { name : 'AZURE_OPENAI_IMAGE_MODEL' , value : imageModelConfig [imageModelChoice ].name }
916- { name : 'AZURE_OPENAI_GPT_IMAGE_ENDPOINT' , value : imageModelChoice != 'none' ? 'https://${aiFoundryAiServicesResourceName }.openai.azure.com/' : '' }
917- { name : 'AZURE_OPENAI_API_VERSION' , value : azureOpenaiAPIVersion }
918- // Azure Cosmos DB Settings
919- { name : 'AZURE_COSMOS_ENDPOINT' , value : 'https://cosmos-${solutionSuffix }.documents.azure.com:443/' }
920- { name : 'AZURE_COSMOS_DATABASE_NAME' , value : cosmosDBDatabaseName }
921- { name : 'AZURE_COSMOS_PRODUCTS_CONTAINER' , value : cosmosDBProductsContainer }
922- { name : 'AZURE_COSMOS_CONVERSATIONS_CONTAINER' , value : cosmosDBConversationsContainer }
923- // Azure Blob Storage Settings
924- { name : 'AZURE_BLOB_ACCOUNT_NAME' , value : storageAccountName }
925- { name : 'AZURE_BLOB_PRODUCT_IMAGES_CONTAINER' , value : productImagesContainer }
926- { name : 'AZURE_BLOB_GENERATED_IMAGES_CONTAINER' , value : generatedImagesContainer }
927- // Azure AI Search Settings
928- { name : 'AZURE_AI_SEARCH_ENDPOINT' , value : 'https://${aiSearchName }.search.windows.net' }
929- { name : 'AZURE_AI_SEARCH_PRODUCTS_INDEX' , value : azureSearchIndex }
930- { name : 'AZURE_AI_SEARCH_IMAGE_INDEX' , value : 'product-images' }
931- // App Settings
932- { name : 'AZURE_CLIENT_ID' , value : userAssignedIdentity .outputs .clientId }
933- { name : 'PORT' , value : '8000' }
934- { name : 'WORKERS' , value : '4' }
935- { name : 'RUNNING_IN_PRODUCTION' , value : 'true' }
936- // Azure AI Foundry Settings
937- { name : 'USE_FOUNDRY' , value : useFoundryMode ? 'true' : 'false' }
938- { name : 'AZURE_AI_PROJECT_ENDPOINT' , value : aiFoundryAiProjectEndpoint }
939- { name : 'AZURE_AI_MODEL_DEPLOYMENT_NAME' , value : gptModelName }
940- { name : 'AZURE_AI_IMAGE_MODEL_DEPLOYMENT' , value : imageModelConfig [imageModelChoice ].name }
941- // Logging Settings
942- { name : 'AZURE_BASIC_LOGGING_LEVEL' , value : 'INFO' }
943- { name : 'AZURE_PACKAGE_LOGGING_LEVEL' , value : 'WARNING' }
944- { name : 'AZURE_LOGGING_PACKAGES' , value : '' }
945- // Application Insights
946- { name : 'APPLICATIONINSIGHTS_CONNECTION_STRING' , value : enableMonitoring ? applicationInsights !.outputs .connectionString : '' }
913+
914+ #disable-next-line no-deployments-resources
915+ resource aciTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry && shouldDeployACI ) {
916+ name : '46d3xbcp.res.containerinstance.${replace ('-..--..-' , '.' , '-' )}.${substring (uniqueString (deployment ().name , solutionLocation ), 0 , 4 )}'
917+ properties : {
918+ mode : 'Incremental'
919+ template : {
920+ '$schema' : 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
921+ contentVersion : '1.0.0.0'
922+ resources : []
923+ }
924+ }
925+ }
926+
927+ resource containerInstance 'Microsoft.ContainerInstance/containerGroups@2025-09-01' = if (shouldDeployACI ) {
928+ name : containerInstanceName
929+ location : solutionLocation
930+ tags : tags
931+ identity : {
932+ type : 'UserAssigned'
933+ userAssignedIdentities : {
934+ '${userAssignedIdentityResourceIdForACI }' : {}
935+ }
936+ }
937+ properties : {
938+ containers : [
939+ {
940+ name : containerInstanceName
941+ properties : {
942+ image : backendImageUrl
943+ resources : {
944+ requests : {
945+ cpu : 2
946+ memoryInGB : 4
947+ }
948+ }
949+ ports : [
950+ {
951+ port : aciPort
952+ protocol : 'TCP'
953+ }
954+ ]
955+ environmentVariables : [
956+ // Azure OpenAI Settings
957+ { name : 'AZURE_OPENAI_ENDPOINT' , value : 'https://${aiFoundryAiServicesResourceName }.openai.azure.com/' }
958+ { name : 'AZURE_OPENAI_GPT_MODEL' , value : gptModelName }
959+ { name : 'AZURE_OPENAI_IMAGE_MODEL' , value : imageModelConfig [imageModelChoice ].name }
960+ { name : 'AZURE_OPENAI_GPT_IMAGE_ENDPOINT' , value : imageModelChoice != 'none' ? 'https://${aiFoundryAiServicesResourceName }.openai.azure.com/' : '' }
961+ { name : 'AZURE_OPENAI_API_VERSION' , value : azureOpenaiAPIVersion }
962+ // Azure Cosmos DB Settings
963+ { name : 'AZURE_COSMOS_ENDPOINT' , value : 'https://cosmos-${solutionSuffix }.documents.azure.com:443/' }
964+ { name : 'AZURE_COSMOS_DATABASE_NAME' , value : cosmosDBDatabaseName }
965+ { name : 'AZURE_COSMOS_PRODUCTS_CONTAINER' , value : cosmosDBProductsContainer }
966+ { name : 'AZURE_COSMOS_CONVERSATIONS_CONTAINER' , value : cosmosDBConversationsContainer }
967+ // Azure Blob Storage Settings
968+ { name : 'AZURE_BLOB_ACCOUNT_NAME' , value : storageAccountName }
969+ { name : 'AZURE_BLOB_PRODUCT_IMAGES_CONTAINER' , value : productImagesContainer }
970+ { name : 'AZURE_BLOB_GENERATED_IMAGES_CONTAINER' , value : generatedImagesContainer }
971+ // Azure AI Search Settings
972+ { name : 'AZURE_AI_SEARCH_ENDPOINT' , value : 'https://${aiSearchName }.search.windows.net' }
973+ { name : 'AZURE_AI_SEARCH_PRODUCTS_INDEX' , value : azureSearchIndex }
974+ { name : 'AZURE_AI_SEARCH_IMAGE_INDEX' , value : 'product-images' }
975+ // App Settings
976+ { name : 'AZURE_CLIENT_ID' , value : userAssignedIdentity .outputs .clientId }
977+ { name : 'PORT' , value : '8000' }
978+ { name : 'WORKERS' , value : '4' }
979+ { name : 'RUNNING_IN_PRODUCTION' , value : 'true' }
980+ // Azure AI Foundry Settings
981+ { name : 'USE_FOUNDRY' , value : useFoundryMode ? 'true' : 'false' }
982+ { name : 'AZURE_AI_PROJECT_ENDPOINT' , value : aiFoundryAiProjectEndpoint }
983+ { name : 'AZURE_AI_MODEL_DEPLOYMENT_NAME' , value : gptModelName }
984+ { name : 'AZURE_AI_IMAGE_MODEL_DEPLOYMENT' , value : imageModelConfig [imageModelChoice ].name }
985+ // Logging Settings
986+ { name : 'AZURE_BASIC_LOGGING_LEVEL' , value : 'INFO' }
987+ { name : 'AZURE_PACKAGE_LOGGING_LEVEL' , value : 'WARNING' }
988+ { name : 'AZURE_LOGGING_PACKAGES' , value : '' }
989+ // Application Insights
990+ { name : 'APPLICATIONINSIGHTS_CONNECTION_STRING' , value : enableMonitoring ? applicationInsights !.outputs .connectionString : '' }
991+ ]
992+ }
993+ }
994+ ]
995+ osType : 'Linux'
996+ restartPolicy : 'Always'
997+ subnetIds : isPrivateNetworking ? [
998+ {
999+ id : virtualNetwork !.outputs .aciSubnetResourceId
1000+ }
1001+ ] : null
1002+ ipAddress : {
1003+ type : isPrivateNetworking ? 'Private' : 'Public'
1004+ ports : [
1005+ {
1006+ port : aciPort
1007+ protocol : 'TCP'
1008+ }
1009+ ]
1010+ dnsNameLabel : isPrivateNetworking ? null : containerInstanceName
1011+ }
1012+ // Managed identity auth for ACR (instead of anonymous pull)
1013+ imageRegistryCredentials : [
1014+ {
1015+ server : containerRegistry .outputs .loginServer
1016+ identity : userAssignedIdentityResourceIdForACI
1017+ }
9471018 ]
9481019 }
9491020}
@@ -1037,13 +1108,13 @@ output AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING string = (enableMonitoring &
10371108output AZURE_ENV_OPENAI_LOCATION string = azureAiServiceLocation
10381109
10391110@description ('Contains Container Instance Name' )
1040- output CONTAINER_INSTANCE_NAME string = shouldDeployACI ? containerInstance !.outputs . name : ''
1111+ output CONTAINER_INSTANCE_NAME string = shouldDeployACI ? containerInstance !.name : ''
10411112
10421113@description ('Contains Container Instance IP Address' )
1043- output CONTAINER_INSTANCE_IP string = shouldDeployACI ? containerInstance !.outputs .ipAddress : ''
1114+ output CONTAINER_INSTANCE_IP string = shouldDeployACI ? containerInstance !.properties .ipAddress . ip : ''
10441115
10451116@description ('Contains Container Instance FQDN (only for non-private networking)' )
1046- output CONTAINER_INSTANCE_FQDN string = (shouldDeployACI && !enablePrivateNetworking ) ? containerInstance !.outputs .fqdn : ''
1117+ output CONTAINER_INSTANCE_FQDN string = (shouldDeployACI && !isPrivateNetworking ) ? containerInstance !.properties . ipAddress .fqdn : ''
10471118
10481119@description ('Contains ACR Name' )
10491120output ACR_NAME string = acrResourceName
0 commit comments