### Summary of Changes
1. Documentation Consolidation:
• Relocated the docs/ folder and all repository .md files (README.md, DEPLOYMENT.md, DOCKER_README.md,
DOMAINS_README.md, GETTING_STARTED.md, CI_CD_PORTAINER_GUIDE.md, ADMIN_DEPLOYMENT_SUMMARY.md,
SECURITY_PRIVATE_FILES.md, handoff.md, azure-project-plan.md, plus service-specific READMEs) into .
• Updated document links in README.md.
2. AI Agent Project Map:
• Created PROJECT_MAP.md in the project root containing complete architecture diagrams, service specs, ports, DB
schemas, environment variables, and developer workflows.
• Added PROJECT_MAP.md to .gitignore.
3. Monitoring UI & Infrastructure Integration:
• Added monitoring_ui service to docker-compose.yml (port 5175).
• Configured Gitea workflow docker-build.yml for monitoring_ui builds.
• Added CORS middleware headers in index.js.
• Added nginx.conf copy step in Dockerfile.
──────
### Recommended Commit Message
docs: consolidate project documentation into monitoring_ui and add agent project map
- Move all markdown documentation files and docs/ folder into monitoring_ui/docs/
- Update documentation hub index (monitoring_ui/docs/README.md) with relative paths
- Generate comprehensive AI agent reference map (PROJECT_MAP.md) in repository root
- Add PROJECT_MAP.md and monitoring_ui build output/env overrides to .gitignore
- Integrate monitoring_ui service into docker-compose.yml and Gitea CI workflow
- Configure CORS handling in employee_api for monitoring dashboard access
──────
### Alternative Short Commit Message
refactor(docs): move docs to monitoring_ui and create AI agent PROJECT_MAP.md
9.5 KiB
Important: This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
Bicep — Container Apps Patterns
Container Apps-specific Bicep patterns. For shared patterns (skeleton, naming, tags, security defaults, data modules), see bicep-patterns.md.
Two-Phase Wiring
Container Apps + ACR requires two-phase deployment (circular dependency: CA needs ACR image, ACR needs CA identity for AcrPull):
- Phase 1: Deploy Container App with placeholder image (
mcr.microsoft.com/azuredocs/containerapps-helloworld:latest). ⛔ Noregistriesblock. The placeholder image is pulled from MCR (public). Useregistries: []. Nativesecrets(literal values) MAY be set in Phase 1 — they have no RBAC dependency. The AcrPull role assignment IS created in Phase 1 — it doesn't affect the placeholder deployment and needs 1–2 minutes to propagate before Phase 2. - Phase 2: Build + push app image to ACR, redeploy with real image +
registries. AcrPull RBAC is already propagated from Phase 1.
⛔ Placeholder image listens on port 80, not your app's port. Set
targetPortconditionally:var effectivePort = containerImage == 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' ? 80 : appPort. Mismatched ports cause "Operation expired" (health probe can't reach container).
⛔
containerImageparam must exist in BOTHmain.bicepAND the container app module. Phase 2 passes--parameters containerImage='...'via CLI — ifmain.biceplacks the param, the override is silently ignored and the placeholder persists.
// In main.bicep: thread containerImage to module
param containerImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
module containerApp './modules/containerapp.bicep' = {
params: { containerImage: containerImage /* ...other params... */ }
}
// In containerapp.bicep:
param containerImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
var isPlaceholder = containerImage == 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
identity: { type: 'SystemAssigned' }
properties: {
configuration: {
ingress: {
external: true
targetPort: isPlaceholder ? 80 : appPort
allowInsecure: false // ⛔ MANDATORY
}
registries: isPlaceholder ? [] : [{ server: acr.properties.loginServer, identity: 'system' }]
secrets: [ { name: 'secret-key', value: secretKey } ] // native CA secrets (no RBAC dep) — NOT KV
}
template: {
containers: [{
image: containerImage
env: [{ name: 'PORT', value: string(isPlaceholder ? 80 : appPort) }]
}]
}
}
}
⛔ Do NOT set
revisionSuffix. Omit it entirely — ARM auto-generates unique revision names. HardcodingrevisionSuffix: 'v1'causes Phase 2 redeploy to fail with "revision with suffix v1 already exists."
AcrPull Role Assignment
⛔ AcrPull role GUID:
7f951dda-4ed3-4680-a7ca-43fe172d538d. Copy verbatim — wrong GUIDs causeRoleDefinitionDoesNotExist.
resource acrPullRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(acr.id, containerApp.id, '7f951dda-4ed3-4680-a7ca-43fe172d538d')
scope: acr
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
principalId: containerApp.identity.principalId
principalType: 'ServicePrincipal'
}
}
Log Analytics Workspace Key
⛔ Use
resource.listKeys(), NOTreference().reference()does not exposeprimarySharedKey.
// ✅ Correct
var laKey = logAnalyticsWorkspace.listKeys().primarySharedKey
// ❌ Wrong
var laKey = reference(logAnalyticsWorkspace.id, '2023-09-01').primarySharedKey
Log Analytics customerId vs resource ID
⛔ Output BOTH
idandcustomerIdfrom the log-analytics module. Container Apps Environment needs the GUIDcustomerId. App Insights needs the ARM resource ID. Do NOT usesplit(workspaceId, '/')[8]— that extracts the workspace name, not the GUID.
// log-analytics.bicep outputs:
output id string = logAnalyticsWorkspace.id // ARM resource ID
output customerId string = logAnalyticsWorkspace.properties.customerId // GUID
output sharedKey string = logAnalyticsWorkspace.listKeys().primarySharedKey
// container-app-environment.bicep:
param workspaceCustomerId string // GUID, NOT resource ID
// ⛔ MUST nest under appLogsConfiguration.destination='log-analytics' — a bare top-level logAnalyticsConfiguration fails deploy (ManagedEnvironmentInvalidSchema). This nesting is the ONLY valid location at EVERY API version (the flat shape was never valid — NOT version drift, so do not chase API-version pins). This is the CA's only log path (no diagnostic-settings module).
properties: {
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: workspaceCustomerId
sharedKey: workspaceSharedKey
}
}
}
// ❌ WRONG: customerId: split(workspaceId, '/')[8]
Ingress & Port Mapping
⛔ Container resource limits: Use decimal format for memory:
'0.5Gi','1Gi','2Gi'— NOT Kubernetes-style'512Mi'. CPU must be typestring:'0.25','0.5','1'. Valid combos:0.25/0.5Gi,0.5/1Gi,0.75/1.5Gi,1/2Gi,1.25/2.5Gi,1.5/3Gi,1.75/3.5Gi,2/4Gi.
⛔ ACR module:
retentionPolicyis Premium-only. For Basic/Standard ACR, omitretentionPolicyentirely — ARM rejects it.
Container Apps Secrets (Native — No Key Vault)
⛔ No Key Vault. Container Apps store app-internal secrets in the app's native
secretsarray with a literalvaluesupplied by an@secure()param at deploy time. NEVER usekeyVaultUrl,@Microsoft.KeyVault(...), or a KVsecretschild resource.
⛔ Only app-internal secrets use
secretRef. Database, cache, and storage access is managed-identity + token — wire their connection parameters as plainenvvalues (host, db name, MI username,sslmode=require), never asecretRef.secretRefis for things likeSECRET_KEY/JWT/third-party API keys.
Native secrets have no RBAC dependency, so they don't need the
isPlaceholdergate that KV secretRefs required (two-phase wiring is still needed for ACR registries + real image — see § Two-Phase Wiring).
@secure()
param secretKey string // generated at deploy, passed via CLI --parameters, never committed
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
identity: {
type: 'SystemAssigned'
}
properties: {
configuration: {
secrets: [
{ name: 'secret-key', value: secretKey } // ⛔ native CA secret — NOT keyVaultUrl
]
}
template: {
containers: [{
env: [
// App-internal secret via secretRef:
{ name: 'SECRET_KEY', secretRef: 'secret-key' }
// Database via managed identity — plain connection params, NO password/secretRef:
{ name: 'PGHOST', value: '${pgName}.postgres.database.azure.com' }
{ name: 'PGUSER', value: containerAppName } // the MI's DB principal name
{ name: 'PGSSLMODE', value: 'require' }
]
}]
}
}
}
⛔ Never use conditional logic (
??, ternary,empty(),union()) to mix plain and secret env vars in a single Bicep loop or array. ARM evaluates ALL property paths in conditional expressions —envVar.secretReferrors on items that don't have that property, producingInvalidTemplate. Instead, define plain and secret env vars as separate arrays and concatenate:env: concat( [ { name: 'PORT', value: '8000' } { name: 'NODE_ENV', value: 'production' } // DB/cache via managed identity — plain connection params, no secretRef: { name: 'PGHOST', value: '${pgName}.postgres.database.azure.com' } { name: 'PGUSER', value: containerAppName } ], [ { name: 'SECRET_KEY', secretRef: 'secret-key' } // app-internal secret only ] )
⛔
principalId: containerApp.identity.principalId(object ID, NOT clientId) for the AcrPull role assignment. See rbac-roles.md for the AcrPull role GUID.
❌ WRONG:
principalId: .clientId(not the object ID) oridentity: containerApp.idin secrets[] (use'system'for system-assigned MI).
App-internal secrets are native CA secrets (literal
valuefrom an@secure()param) — there is no Key Vault, no KV role assignment, and no secret seeding into KV.
Multi-Container Internal DNS
Container Apps in the same environment communicate via internal DNS: http://{container-app-name}. Set via env vars:
env: [
{ name: 'API_URL', value: 'http://${apiContainerApp.name}' }
{ name: 'WORKER_URL', value: 'http://${workerContainerApp.name}' }
]
No ingress needed for internal-only services — set ingress.external: false or omit ingress entirely.
Networking
⛔ Subnets MUST be defined inline in VNet
properties.subnets[], NOT as separateMicrosoft.Network/virtualNetworks/subnetschild resources. Separate child resources causeInUseSubnetCannotBeDeletedon redeploy when NICs are attached.