Skip to content

Commit 2234cb6

Browse files
committed
Add second container for agent
1 parent 617952c commit 2234cb6

File tree

6 files changed

+166
-26
lines changed

6 files changed

+166
-26
lines changed

agents/Dockerfile

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# ------------------- Stage 1: Build Stage ------------------------------
2+
# We use Alpine for smaller image size (~329MB vs ~431MB for Debian slim).
3+
# Trade-off: We must install build tools to compile native extensions (cryptography, etc.)
4+
# since pre-built musl wheels aren't available. Debian -slim would skip compilation
5+
# but produces a larger final image due to glibc wheel sizes.
6+
FROM python:3.13-alpine AS build
7+
8+
# Install build dependencies for packages with native extensions (cryptography, etc.)
9+
# https://cryptography.io/en/latest/installation/#building-cryptography-on-linux
10+
RUN apk add --no-cache gcc g++ musl-dev python3-dev libffi-dev openssl-dev cargo pkgconfig
11+
12+
COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /uvx /bin/
13+
14+
WORKDIR /code
15+
16+
# Install dependencies first (for layer caching)
17+
RUN --mount=type=cache,target=/root/.cache/uv \
18+
--mount=type=bind,source=uv.lock,target=uv.lock \
19+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
20+
uv sync --locked --no-install-project
21+
22+
# Copy the project and sync
23+
COPY . .
24+
RUN --mount=type=cache,target=/root/.cache/uv \
25+
uv sync --locked
26+
27+
# ------------------- Stage 2: Final Stage ------------------------------
28+
FROM python:3.13-alpine AS final
29+
30+
RUN addgroup -S app && adduser -S app -G app
31+
32+
COPY --from=build --chown=app:app /code /code
33+
34+
WORKDIR /code/agents
35+
USER app
36+
37+
ENV PATH="/code/.venv/bin:$PATH"
38+
39+
ENTRYPOINT ["python", "agentframework_http.py"]

azure.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ metadata:
55
66
services:
77
# Not using remoteBuild due to private endpoint usage
8-
aca:
8+
server:
99
project: .
1010
language: docker
1111
host: containerapp
1212
docker:
1313
path: ./servers/Dockerfile
1414
context: .
15+
agent:
16+
project: .
17+
language: docker
18+
host: containerapp
19+
docker:
20+
path: ./agents/Dockerfile
21+
context: .
1522
hooks:
1623
postprovision:
1724
posix:
@@ -21,4 +28,4 @@ hooks:
2128
windows:
2229
shell: pwsh
2330
run: ./infra/write_env.ps1
24-
continueOnError: true
31+
continueOnError: true

infra/agent.bicep

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
param name string
2+
param location string = resourceGroup().location
3+
param tags object = {}
4+
5+
param identityName string
6+
param containerAppsEnvironmentName string
7+
param containerRegistryName string
8+
param serviceName string = 'agent'
9+
param exists bool
10+
param openAiDeploymentName string
11+
param openAiEndpoint string
12+
param mcpServerUrl string
13+
14+
resource agentIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
15+
name: identityName
16+
location: location
17+
}
18+
19+
module app 'core/host/container-app-upsert.bicep' = {
20+
name: '${serviceName}-container-app-module'
21+
params: {
22+
name: name
23+
location: location
24+
tags: union(tags, { 'azd-service-name': serviceName })
25+
identityName: agentIdentity.name
26+
exists: exists
27+
containerAppsEnvironmentName: containerAppsEnvironmentName
28+
containerRegistryName: containerRegistryName
29+
ingressEnabled: false
30+
env: [
31+
{
32+
name: 'AZURE_OPENAI_CHAT_DEPLOYMENT'
33+
value: openAiDeploymentName
34+
}
35+
{
36+
name: 'AZURE_OPENAI_ENDPOINT'
37+
value: openAiEndpoint
38+
}
39+
{
40+
name: 'API_HOST'
41+
value: 'azure'
42+
}
43+
{
44+
name: 'AZURE_CLIENT_ID'
45+
value: agentIdentity.properties.clientId
46+
}
47+
{
48+
name: 'MCP_SERVER_URL'
49+
value: mcpServerUrl
50+
}
51+
]
52+
}
53+
}
54+
55+
output identityPrincipalId string = agentIdentity.properties.principalId
56+
output name string = app.outputs.name
57+
output hostName string = app.outputs.hostName
58+
output uri string = app.outputs.uri
59+
output imageName string = app.outputs.imageName

infra/core/host/container-app.bicep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,5 +124,5 @@ resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-pr
124124
output defaultDomain string = containerAppsEnvironment.properties.defaultDomain
125125
output imageName string = imageName
126126
output name string = app.name
127-
output hostName string = app.properties.configuration.ingress.fqdn
127+
output hostName string = ingressEnabled ? app.properties.configuration.ingress.fqdn : ''
128128
output uri string = ingressEnabled ? 'https://${app.properties.configuration.ingress.fqdn}' : ''

infra/main.bicep

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ param location string
1212
@description('Id of the user or app to assign application roles')
1313
param principalId string = ''
1414

15-
param acaExists bool = false
15+
param serverExists bool = false
16+
17+
param agentExists bool = false
1618

1719
@description('Location for the OpenAI resource group')
1820
@allowed([
@@ -653,23 +655,41 @@ module cosmosDbPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.
653655
}
654656
}
655657

656-
// Container app frontend
657-
module aca 'aca.bicep' = {
658-
name: 'aca'
658+
// Container app for MCP server
659+
module server 'server.bicep' = {
660+
name: 'server'
659661
scope: resourceGroup
660662
params: {
661-
name: replace('${take(prefix,19)}-ca', '--', '-')
663+
name: replace('${take(prefix,15)}-server', '--', '-')
662664
location: location
663665
tags: tags
664-
identityName: '${prefix}-id-aca'
666+
identityName: '${prefix}-id-server'
665667
containerAppsEnvironmentName: containerApps.outputs.environmentName
666668
containerRegistryName: containerApps.outputs.registryName
667669
openAiDeploymentName: openAiDeploymentName
668670
openAiEndpoint: openAi.outputs.endpoint
669671
cosmosDbAccount: cosmosDb.outputs.name
670672
cosmosDbDatabase: cosmosDbDatabaseName
671673
cosmosDbContainer: cosmosDbContainerName
672-
exists: acaExists
674+
exists: serverExists
675+
}
676+
}
677+
678+
// Container app for agent
679+
module agent 'agent.bicep' = {
680+
name: 'agent'
681+
scope: resourceGroup
682+
params: {
683+
name: replace('${take(prefix,15)}-agent', '--', '-')
684+
location: location
685+
tags: tags
686+
identityName: '${prefix}-id-agent'
687+
containerAppsEnvironmentName: containerApps.outputs.environmentName
688+
containerRegistryName: containerApps.outputs.registryName
689+
openAiDeploymentName: openAiDeploymentName
690+
openAiEndpoint: openAi.outputs.endpoint
691+
mcpServerUrl: '${server.outputs.uri}/mcp/'
692+
exists: agentExists
673693
}
674694
}
675695

@@ -683,11 +703,21 @@ module openAiRoleUser 'core/security/role.bicep' = {
683703
}
684704
}
685705

686-
module openAiRoleBackend 'core/security/role.bicep' = {
706+
module openAiRoleServer 'core/security/role.bicep' = {
707+
scope: resourceGroup
708+
name: 'openai-role-server'
709+
params: {
710+
principalId: server.outputs.identityPrincipalId
711+
roleDefinitionId: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' // Cognitive Services OpenAI User
712+
principalType: 'ServicePrincipal'
713+
}
714+
}
715+
716+
module openAiRoleAgent 'core/security/role.bicep' = {
687717
scope: resourceGroup
688-
name: 'openai-role-backend'
718+
name: 'openai-role-agent'
689719
params: {
690-
principalId: aca.outputs.identityPrincipalId
720+
principalId: agent.outputs.identityPrincipalId
691721
roleDefinitionId: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' // Cognitive Services OpenAI User
692722
principalType: 'ServicePrincipal'
693723
}
@@ -704,13 +734,13 @@ module cosmosDbRoleUser 'core/security/documentdb-sql-role.bicep' = {
704734
}
705735
}
706736

707-
// Cosmos DB Data Contributor role for backend
708-
module cosmosDbRoleBackend 'core/security/documentdb-sql-role.bicep' = {
737+
// Cosmos DB Data Contributor role for server
738+
module cosmosDbRoleServer 'core/security/documentdb-sql-role.bicep' = {
709739
scope: resourceGroup
710-
name: 'cosmosdb-role-backend'
740+
name: 'cosmosdb-role-server'
711741
params: {
712742
databaseAccountName: cosmosDb.outputs.name
713-
principalId: aca.outputs.identityPrincipalId
743+
principalId: server.outputs.identityPrincipalId
714744
roleDefinitionId: '/${subscription().id}/resourceGroups/${resourceGroup.name}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosDb.outputs.name}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002'
715745
}
716746
}
@@ -725,10 +755,15 @@ output AZURE_OPENAI_ENDPOINT string = openAi.outputs.endpoint
725755
output AZURE_OPENAI_RESOURCE string = openAi.outputs.name
726756
output AZURE_OPENAI_RESOURCE_LOCATION string = openAi.outputs.location
727757

728-
output SERVICE_ACA_IDENTITY_PRINCIPAL_ID string = aca.outputs.identityPrincipalId
729-
output SERVICE_ACA_NAME string = aca.outputs.name
730-
output SERVICE_ACA_URI string = aca.outputs.uri
731-
output SERVICE_ACA_IMAGE_NAME string = aca.outputs.imageName
758+
output SERVICE_SERVER_IDENTITY_PRINCIPAL_ID string = server.outputs.identityPrincipalId
759+
output SERVICE_SERVER_NAME string = server.outputs.name
760+
output SERVICE_SERVER_URI string = server.outputs.uri
761+
output SERVICE_SERVER_IMAGE_NAME string = server.outputs.imageName
762+
763+
output SERVICE_AGENT_IDENTITY_PRINCIPAL_ID string = agent.outputs.identityPrincipalId
764+
output SERVICE_AGENT_NAME string = agent.outputs.name
765+
output SERVICE_AGENT_URI string = agent.outputs.uri
766+
output SERVICE_AGENT_IMAGE_NAME string = agent.outputs.imageName
732767

733768
output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName
734769
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerApps.outputs.registryLoginServer

infra/aca.bicep renamed to infra/server.bicep

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ param tags object = {}
55
param identityName string
66
param containerAppsEnvironmentName string
77
param containerRegistryName string
8-
param serviceName string = 'aca'
8+
param serviceName string = 'server'
99
param exists bool
1010
param openAiDeploymentName string
1111
param openAiEndpoint string
1212
param cosmosDbAccount string
1313
param cosmosDbDatabase string
1414
param cosmosDbContainer string
1515

16-
resource acaIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
16+
resource serverIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
1717
name: identityName
1818
location: location
1919
}
@@ -24,7 +24,7 @@ module app 'core/host/container-app-upsert.bicep' = {
2424
name: name
2525
location: location
2626
tags: union(tags, { 'azd-service-name': serviceName })
27-
identityName: acaIdentity.name
27+
identityName: serverIdentity.name
2828
exists: exists
2929
containerAppsEnvironmentName: containerAppsEnvironmentName
3030
containerRegistryName: containerRegistryName
@@ -44,7 +44,7 @@ module app 'core/host/container-app-upsert.bicep' = {
4444
}
4545
{
4646
name: 'AZURE_CLIENT_ID'
47-
value: acaIdentity.properties.clientId
47+
value: serverIdentity.properties.clientId
4848
}
4949
{
5050
name: 'AZURE_COSMOSDB_ACCOUNT'
@@ -63,7 +63,7 @@ module app 'core/host/container-app-upsert.bicep' = {
6363
}
6464
}
6565

66-
output identityPrincipalId string = acaIdentity.properties.principalId
66+
output identityPrincipalId string = serverIdentity.properties.principalId
6767
output name string = app.outputs.name
6868
output hostName string = app.outputs.hostName
6969
output uri string = app.outputs.uri

0 commit comments

Comments
 (0)