Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/DeploymentGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ Review the configuration options below. You can customize any settings that meet
| **Framework** | Basic configuration | [Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/) |
| **Features** | Core functionality | Reliability, security, operational excellence |

When using the Production/WAF deployment (`enablePrivateNetworking=true`), networking is configured as follows:

- Backend Container App endpoints are internal-only (`ingressExternal=false`) and not publicly reachable.
- Container Apps Environment is deployed in internal mode with VNet integration.
- The web frontend remains public and routes browser API traffic through same-origin `/api` proxying to the private backend over VNet.
- Private DNS is configured for the internal Container Apps Environment domain.

**To use production configuration:**

Copy the contents from the production configuration file to your main parameters file:
Expand Down
2 changes: 2 additions & 0 deletions docs/TechnicalArchitecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ The API also provides schema management, schema set (collection) management, and
### Claim Process Monitor Web
Using Azure Container App, this app acts as the UI for the process monitoring queue. The app is built with React and TypeScript. It acts as an API client to create an experience for uploading new documents, creating and managing claim batches, monitoring current and historical processes, and reviewing output results including summarization and gap analysis.

In WAF/private networking deployments (`enablePrivateNetworking=true`), the frontend remains public while backend APIs are internal-only. The web container proxies `/api/*` traffic to the private API Container App over VNet so backend endpoints are not directly exposed to the public internet.

### App Configuration
Using Azure App Configuration, app settings and configurations are centralized and used with the Content Processor, Content Process API, Content Process Workflow, and Claim Process Monitor Web.

Expand Down
39 changes: 36 additions & 3 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,8 @@ module avmContainerAppEnv 'br/public:avm/res/app/managed-environment:0.13.2' = {
}
]
enableTelemetry: enableTelemetry
publicNetworkAccess: 'Enabled' // Always enabled for Container Apps Environment
publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled'
internal: enablePrivateNetworking ? true : false
Comment on lines +934 to +935
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the Container Apps Environment to internal: true and publicNetworkAccess: 'Disabled' means the environment (and therefore the web Container App inside it) will be reachable only within the VNet. This conflicts with the template still configuring the web Container App ingress as external (ingressExternal: true later in this file). Either keep the environment external and make only the API app's ingress internal, or also make the web app ingress/internal routing consistent with an internal environment (and document the required WAF/App Gateway in front).

Suggested change
publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled'
internal: enablePrivateNetworking ? true : false
publicNetworkAccess: 'Enabled'
internal: false

Copilot uses AI. Check for mistakes.

// <========== WAF related parameters

Expand All @@ -944,6 +945,34 @@ module avmContainerAppEnv 'br/public:avm/res/app/managed-environment:0.13.2' = {
}
}

// ========== Private DNS Zone for internal Container App Environment ========== //
// When the CAE is internal, its FQDN is resolvable only within the VNet via this zone.
module caeDnsZone 'br/public:avm/res/network/private-dns-zone:0.8.0' = if (enablePrivateNetworking) {
name: take('avm.res.network.private-dns-zone.cae.${solutionSuffix}', 64)
params: {
name: avmContainerAppEnv.outputs.defaultDomain
tags: tags
enableTelemetry: enableTelemetry
a: [
{
name: '*'
aRecords: [
{
ipv4Address: avmContainerAppEnv.outputs.staticIp
}
]
ttl: 300
}
]
virtualNetworkLinks: [
{
name: take('vnetlink-vnet-${solutionSuffix}-cae', 64)
virtualNetworkResourceId: virtualNetwork!.outputs.resourceId
}
]
}
}

// //=========== Managed Identity for Container Registry ========== //
module avmContainerRegistryReader 'br/public:avm/res/managed-identity/user-assigned-identity:0.5.0' = {
name: take('avm.res.managed-identity.user-assigned-identity.${solutionSuffix}', 64)
Expand Down Expand Up @@ -1132,7 +1161,7 @@ module avmContainerApp_API 'br/public:avm/res/app/container-app:0.22.1' = {
}
]
}
ingressExternal: true
ingressExternal: enablePrivateNetworking ? false : true
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With ingressExternal set to false in private networking mode, the API Container App FQDN will no longer be reachable from outside the VNet. The repo's post-deployment scripts currently use CONTAINER_API_APP_FQDN to wait for readiness and register schemas from the deployer's machine; that flow will fail for enablePrivateNetworking=true. Consider updating the post-deployment workflow to call the API through the web app's new /api proxy (using CONTAINER_WEB_APP_FQDN) or to execute the registration from within the VNet (e.g., via the jumpbox).

Suggested change
ingressExternal: enablePrivateNetworking ? false : true
ingressExternal: true

Copilot uses AI. Check for mistakes.
activeRevisionsMode: 'Single'
ingressTransport: 'auto'
corsPolicy: {
Expand Down Expand Up @@ -1201,6 +1230,10 @@ module avmContainerApp_Web 'br/public:avm/res/app/container-app:0.22.1' = {
env: [
{
name: 'APP_API_BASE_URL'
value: enablePrivateNetworking ? '/api' : 'https://${avmContainerApp_API.outputs.fqdn}'
}
{
name: 'APP_BACKEND_API_URL'
value: 'https://${avmContainerApp_API.outputs.fqdn}'
}
{
Expand Down Expand Up @@ -1808,7 +1841,7 @@ module avmContainerApp_API_update 'br/public:avm/res/app/container-app:0.22.1' =
}
]
}
ingressExternal: true
ingressExternal: enablePrivateNetworking ? false : true
activeRevisionsMode: 'Single'
ingressTransport: 'auto'
corsPolicy: {
Expand Down
18 changes: 18 additions & 0 deletions infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@
"existingFoundryProjectResourceId": {
"value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}"
},
"enableMonitoring": {
"value": true
},
"enablePrivateNetworking": {
"value": true
},
"enableScalability": {
"value": true
},
"vmAdminUsername": {
"value": "${AZURE_ENV_VM_ADMIN_USERNAME}"
},
"vmAdminPassword": {
"value": "${AZURE_ENV_VM_ADMIN_PASSWORD}"
},
"vmSize": {
"value": "${AZURE_ENV_VM_SIZE}"
},
"containerRegistryEndpoint": {
"value": "${AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT}"
},
Expand Down
39 changes: 36 additions & 3 deletions infra/main_custom.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,8 @@ module avmContainerAppEnv 'br/public:avm/res/app/managed-environment:0.13.2' = {
}
]
enableTelemetry: enableTelemetry
publicNetworkAccess: 'Enabled' // Always enabled for Container Apps Environment
publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled'
internal: enablePrivateNetworking ? true : false
Comment on lines +937 to +938
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the Container Apps Environment to internal: true and publicNetworkAccess: 'Disabled' means the environment (and therefore the web Container App inside it) will be reachable only within the VNet. This conflicts with the template still configuring the web Container App ingress as external (ingressExternal: true later in this file). Either keep the environment external and make only the API app's ingress internal, or also make the web app ingress/internal routing consistent with an internal environment (and document the required WAF/App Gateway in front).

Suggested change
publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled'
internal: enablePrivateNetworking ? true : false
publicNetworkAccess: 'Enabled'
internal: false

Copilot uses AI. Check for mistakes.

// <========== WAF related parameters

Expand All @@ -947,6 +948,34 @@ module avmContainerAppEnv 'br/public:avm/res/app/managed-environment:0.13.2' = {
}
}

// ========== Private DNS Zone for internal Container App Environment ========== //
// When the CAE is internal, its FQDN is resolvable only within the VNet via this zone.
module caeDnsZone 'br/public:avm/res/network/private-dns-zone:0.8.0' = if (enablePrivateNetworking) {
name: take('avm.res.network.private-dns-zone.cae.${solutionSuffix}', 64)
params: {
name: avmContainerAppEnv.outputs.defaultDomain
tags: tags
enableTelemetry: enableTelemetry
a: [
{
name: '*'
aRecords: [
{
ipv4Address: avmContainerAppEnv.outputs.staticIp
}
]
ttl: 300
}
]
virtualNetworkLinks: [
{
name: take('vnetlink-vnet-${solutionSuffix}-cae', 64)
virtualNetworkResourceId: virtualNetwork!.outputs.resourceId
}
]
}
}

// //=========== Managed Identity for Container Registry ========== //
module avmContainerRegistryReader 'br/public:avm/res/managed-identity/user-assigned-identity:0.5.0' = {
name: take('avm.res.managed-identity.user-assigned-identity.${solutionSuffix}', 64)
Expand Down Expand Up @@ -1145,7 +1174,7 @@ module avmContainerApp_API 'br/public:avm/res/app/container-app:0.22.1' = {
}
]
}
ingressExternal: true
ingressExternal: enablePrivateNetworking ? false : true
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With ingressExternal set to false in private networking mode, the API Container App FQDN will no longer be reachable from outside the VNet. The repo's post-deployment scripts currently use CONTAINER_API_APP_FQDN to wait for readiness and register schemas from the deployer's machine; that flow will fail for enablePrivateNetworking=true. Consider updating the post-deployment workflow to call the API through the web app's new /api proxy (using CONTAINER_WEB_APP_FQDN) or to execute the registration from within the VNet (e.g., via the jumpbox).

Suggested change
ingressExternal: enablePrivateNetworking ? false : true
ingressExternal: true

Copilot uses AI. Check for mistakes.
activeRevisionsMode: 'Single'
ingressTransport: 'auto'
corsPolicy: {
Expand Down Expand Up @@ -1219,6 +1248,10 @@ module avmContainerApp_Web 'br/public:avm/res/app/container-app:0.22.1' = {
env: [
{
name: 'APP_API_BASE_URL'
value: enablePrivateNetworking ? '/api' : 'https://${avmContainerApp_API.outputs.fqdn}'
}
{
name: 'APP_BACKEND_API_URL'
value: 'https://${avmContainerApp_API.outputs.fqdn}'
}
{
Expand Down Expand Up @@ -1841,7 +1874,7 @@ module avmContainerApp_API_update 'br/public:avm/res/app/container-app:0.22.1' =
}
]
}
ingressExternal: true
ingressExternal: enablePrivateNetworking ? false : true
activeRevisionsMode: 'Single'
ingressTransport: 'auto'
corsPolicy: {
Expand Down
1 change: 1 addition & 0 deletions src/ContentProcessorWeb/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ do
echo $key=$value
# Use sed to replace only the exact matches of the key
find /usr/share/nginx/html -type f -exec sed -i "s|\b${key}\b|${value}|g" '{}' +
sed -i "s|\b${key}\b|${value}|g" /etc/nginx/nginx.conf
done
echo 'done'
12 changes: 12 additions & 0 deletions src/ContentProcessorWeb/nginx-custom.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ http {
listen 3000;
server_name localhost;

# Route browser API calls through the web container so private backend
# endpoints remain internal-only in WAF/private networking deployments.
location /api/ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass APP_BACKEND_API_URL/;
}
Comment on lines +21 to +31
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

location /api/ relies on proxy_pass APP_BACKEND_API_URL/; being substituted at runtime. If APP_BACKEND_API_URL is not set in the container environment, env.sh will not replace the placeholder and nginx will fail to start due to an invalid proxy_pass URL. Ensure all deployment paths that run this image always set APP_BACKEND_API_URL (even when the app is not using the /api proxy), or adjust the nginx config/entrypoint to provide a safe default when the variable is missing.

Copilot uses AI. Check for mistakes.

location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
Expand Down
Loading