Azure Container Apps + Dapr: la arquitectura completa
En pocas palabras: La arquitectura de referencia completa de Martin Oehlert combina Front Door Premium, API Management Standard v2, Cosmos DB y Service Bus Premium con Dapr en Azure Container Apps, corriendo dos servicios .NET 10. Las capas delante del código cuestan unos USD 1.730 mensuales en West Europe, antes de procesar el primer request.
Martin Oehlert publicó un artículo de cierre que reúne en una sola pieza su serie sobre arquitectura Azure Container Apps Dapr para .NET, con el stack completo armado: Front Door Premium, API Management Standard v2, Cosmos DB, Service Bus Premium y dos servicios en .NET 10. El costo de las capas delante del código ronda los USD 1.730 por mes en West Europe, antes de que corra la primera línea de la API.
Dapr (Distributed Application Runtime) es un runtime open source impulsado originalmente por Microsoft que corre como sidecar junto a cada servicio y le da invocación entre servicios, manejo de estado y mensajería sin que el código sepa en qué nube está parado. Azure Container Apps es el servicio serverless de contenedores de Azure que, en esta arquitectura, administra ese sidecar por vos: vos no instalás Dapr ni elegís su versión en producción.
En este artículo:
- En 30 segundos
- ¿Qué capas componen esta arquitectura de referencia de Azure Container Apps y Dapr?
- ¿Cuánto cuesta correr esta arquitectura en Azure?
- ¿Cómo viaja una petición POST /orders a través de los siete saltos de la arquitectura?
- ¿Qué rol cumple cada herramienta: Aspire, Dapr, Container Apps y Terraform?
- ¿Por qué fallan los health checks de Aspire en producción sobre Container Apps?
- ¿Por qué el pipeline de trazas de OpenTelemetry es el eslabón más frágil?
- ¿Qué partes de la serie original dejó sin cubrir este artículo?
- Errores comunes al armar esta arquitectura
- Preguntas Frecuentes
- Conclusión
- Fuentes
En 30 segundos
- El stack completo (Front Door Premium + API Management Standard v2 + networking) cuesta cerca de USD 1.730 por mes en West Europe, sin contar la API todavía.
- La API .NET sobre Container Apps cuesta entre USD 20 y USD 140 por mes según el tráfico, de 1 millón a 100 millones de requests.
- Los health checks de Aspire fallan en producción: ServiceDefaults solo mapea
/healthy/aliveen Development, pero Terraform sondea/healthz/livey/healthz/ready. - Ningún servicio usa claves: Cosmos DB, Service Bus y los componentes de Dapr se autentican con identidades administradas (managed identities).
- El pipeline de trazas es el único salto sin identidad administrada: el agente OpenTelemetry se autentica con connection string.
¿Qué capas componen esta arquitectura de referencia de Azure Container Apps y Dapr?
La arquitectura apila seis piezas de Azure en orden: Front Door Premium en el borde, API Management Standard v2 detrás con un origen Private Link, y un entorno interno de Container Apps en tu propia VNet corriendo dos servicios ASP.NET Core en .NET 10, cada uno con su sidecar Dapr administrado. Cosmos DB y un namespace de Service Bus Premium quedan detrás de endpoints privados y no aceptan claves, solo identidades administradas.
Ojo con el detalle de los nombres: Oehlert llama a los servicios orders-api e inventory-api, al almacén de estado statestore y al broker orders-pubsub. El ejemplo de Aspire de una entrega anterior de la serie usa order-service e inventory-service, y otro ejemplo llama orderstore al almacén de estado. Las cadenas cambian, el cableado es el mismo, pero si estás siguiendo la serie completa y copiás fragmentos de distintas partes, te vas a confundir en algún punto.
Front Door va en tier Premium por dos razones que cierran el tema solas: los managed rule sets del WAF solo existen en Premium, y los orígenes Private Link también. API Management Standard v2 es el tier más barato que acepta un endpoint privado entrante y además integra salida hacia una VNet, y esta topología necesita las dos cosas a la vez. Sobre eso hablamos en conectarte por SSH a una VM de Azure.
¿Cuánto cuesta correr esta arquitectura en Azure?

Las capas delante del código cuestan cerca de USD 1.730 por mes en West Europe, antes de que arranque el primer contenedor. La API .NET detrás de esas capas cuesta entre USD 20 y USD 140 por mes según el volumen, de 1 millón a 100 millones de requests mensuales.
¿Y qué significa eso en plata? Que el argumento a favor de esta arquitectura nunca puede ser el cómputo. En cómputo puro, Container Apps no le gana a Azure Functions en ningún escenario de los que mide la serie. El valor está en otro lado: identidades administradas en toda la cadena, networking privado de punta a punta, y un sidecar que resuelve invocación de servicios sin que escribas código de plataforma.
Si tu equipo en Argentina está evaluando este nivel de complejidad para un proyecto que todavía no necesita Front Door Premium ni Private Link en cada salto, vale la pena preguntarse si no estás pagando por una arquitectura de empresa grande para un problema de empresa chica. Para hosting y dominios más simples, sin este nivel de sobreingeniería, donweb.com es una alternativa más acorde al tamaño real del proyecto.
¿Cómo viaja una petición POST /orders a través de los siete saltos de la arquitectura?
Un POST a /orders pasa por siete saltos antes de volver al partner que lo mandó, y en cada uno cambia el mecanismo de confianza. Seguir ese camino completo es la mejor forma de entender para qué sirve cada capa del stack.
- Front Door termina TLS y corre el WAF. La petición sale de Front Door con un header
X-Azure-FDIDy nada que pruebe quién la mandó todavía. - Private Link entra a API Management. El gateway confía en el salto por la ruta de red y un chequeo del header, no por una credencial.
- API Management llama a la app con su propia identidad. Valida el token del que llamó, aplica límites por caller, y después pide un token para
orders-apicon la identidad asignada por el sistema del gateway. - orders-api le pregunta a inventory-api si hay stock, a través del sidecar. Los dos sidecars se resuelven entre sí dentro del entorno y envuelven la llamada en mTLS mutuo sin que configures nada vos.
- SaveStateAsync escribe en Cosmos DB. El código nombra un componente, nunca una cuenta, y el permiso detrás es el rol de datos Cosmos DB Built-in Data Contributor.
- La orden se anuncia en un tópico de Service Bus. El componente
orders-pubsubusapubsub.azure.servicebus.topics, y una regla de KEDA escalainventory-apisegún la profundidad de la suscripción. - Cada span va al agente de OpenTelemetry del entorno. Es el único salto que no corre con identidad administrada.
Nadie verifica qué app mandó la llamada en el salto cuatro: las políticas de control de acceso de Dapr viven en una parte del spec que Container Apps no soporta, así que cualquier app con Dapr habilitado dentro del mismo entorno puede llamar a inventory-api. Tomalo con pinzas si estás pensando en multi-tenant dentro del mismo entorno.
¿Qué rol cumple cada herramienta: Aspire, Dapr, Container Apps y Terraform?
Cuatro herramientas tocan este stack y cada una es dueña de una etapa distinta. Aspire maneja el tiempo de desarrollo, Dapr el contrato entre la app y la plataforma, Container Apps todo lo que pasa en runtime que no es tu código, y Terraform la infraestructura que Aspire nunca te hizo escribir. Te puede servir nuestra cobertura de certificaciones de Azure según tu rol.
Aspire arranca los dos servicios, los dos sidecars y un almacén de estado con Redis con un solo F5, y le pasa a cada proceso su endpoint OTLP para que el dashboard dibuje una sola traza de todo. Ahí se termina su trabajo: nada en Azure corre el AppHost. Lo que sobrevive a producción es ServiceDefaults, compilado en cada servicio.
Dapr define el contrato con nombres de componentes y app ids, que es toda la API que tu código ve. statestore es Redis en tu laptop y Cosmos DB en Azure, y el handler de la lógica de negocio no cambia porque el cambio pasa en un YAML de un lado y un recurso de Terraform del otro. Container Apps administra el sidecar en ambas direcciones: no instalás daprd y tampoco elegís su versión, la plataforma corre la 1.16 mientras que un dapr init en tu máquina te da la 1.18 (la desincronización de versiones es algo que conviene chequear antes de asumir que el comportamiento es idéntico).
Terraform despliega la VNet, el entorno, las identidades, cada permiso otorgado y los componentes con sus scopes. El detalle importante: la parte de Terraform de la serie solo despliega orders-api. No hay inventory-api, no hay Front Door y no hay API Management ahí, y este artículo tampoco los agrega.
¿Por qué fallan los health checks de Aspire en producción sobre Container Apps?
Fallan porque ServiceDefaults solo mapea endpoints de salud cuando el ambiente es Development, y en producción Container Apps corre con ASPNETCORE_ENVIRONMENT=Production, así que no hay ningún endpoint mapeado para que Terraform sondee.
Ponele que ya desplegaste todo, Terraform definió liveness_probe y readiness_probe apuntando a /healthz/live y /healthz/ready, pero el método MapDefaultEndpoints solo registra /health y /alive, y solo si app.Environment.IsDevelopment() da verdadero. En producción esa condición es falsa, no se mapea nada, y Container Apps considera saludable cualquier respuesta entre 200 y 399. Un 404 falla las dos sondas al mismo tiempo: liveness reinicia el contenedor, readiness lo deja afuera de la rotación, y el primer síntoma es una revisión que nunca pasa a estado healthy mientras los logs de la app no mencionan nada relacionado con health checks. Para más detalles técnicos, mirá aprovisionar infraestructura en Azure con Terraform.
¿Alguien de la serie original cruzó un archivo con el otro antes de publicar? No. Una entrega avisó que los endpoints desaparecen fuera de Development, otra entrega probó rutas con el prefijo /healthz/, que es la convención que usa el chequeo de salud de apps de Dapr y la mayoría de ejemplos de Kubernetes, y ninguna de las dos partes chequeó una contra la otra.
¿Por qué el pipeline de trazas de OpenTelemetry es el eslabón más frágil?
Es el eslabón más frágil porque es el único salto de los siete que no usa identidad administrada: el agente OTLP del entorno se autentica con una connection string, lo que obliga a mantener la autenticación local habilitada en el recurso de Application Insights.
El lado de la app no necesita cambios. ServiceDefaults decide a dónde va la telemetría según una sola variable de entorno, OTEL_EXPORTER_OTLP_ENDPOINT. En tu laptop, el AppHost la apunta al dashboard de Aspire. En Azure, apunta al agente administrado del entorno. El tema es que una variable de entrega anterior de la serie, daprAIConnectionString, termina dividiendo el pipeline de trazas en dos: las spans de tu app van por un lado y las de daprd (el proceso del sidecar) pueden terminar yendo por otro si esa variable no quedó configurada igual en los dos lugares. El resultado es que ves trazas incompletas en Application Insights y nada en los logs te dice por qué falta la mitad de la historia.
¿Qué partes de la serie original dejó sin cubrir este artículo?
Quedó sin cubrir el despliegue completo de infraestructura con Terraform: la parte dedicada a Terraform solo levanta orders-api, sin inventory-api, sin Front Door y sin API Management, y este artículo no agrega ninguno de los tres a ese código.
Subís el servicio, probás la cadena de invocación en tu laptop con Aspire, funciona bárbaro, lo mandás a producción con Terraform y de repente la mitad de los nombres no coinciden entre partes: una entrega llama order-service a lo que esta llama orders-api, otra llama orderstore a lo que acá es statestore, y si copiás fragmentos de distintos capítulos de la serie sin revisar los nombres, te vas a encontrar con componentes de Dapr que apuntan a recursos que no existen en tu Terraform. Más contexto en configurar FSLogix con Azure NetApp Files.
Errores comunes al armar esta arquitectura
- Asumir que los health checks de Aspire sirven en producción tal cual. Hay que escribir un endpoint de salud propio que responda en cualquier ambiente, no solo en Development, y que use las mismas rutas que sondea Terraform.
- Dar por hecho que Dapr controla quién llama a quién. Container Apps no soporta las políticas de control de acceso de Dapr, así que cualquier app del mismo entorno con Dapr habilitado puede invocar a cualquier otra. Si necesitás aislar servicios, esa segmentación tiene que vivir en otro lado, no en Dapr.
- Copiar nombres de componentes entre partes de una serie o entre entornos sin revisar.
statestore,orderstore,order-service: son nombres distintos para piezas que cumplen el mismo rol, y mezclarlos rompe el despliegue sin un mensaje de error claro. - Olvidarse de la identidad separada para KEDA. La regla de escalado por profundidad de suscripción necesita su propia referencia de identidad, incluso cuando nombra la misma identidad que ya usa el sidecar, o no puede leer la métrica que la escala.
Preguntas Frecuentes
¿Qué es Dapr y cómo se integra con Azure Container Apps?
Dapr es un runtime que corre como sidecar junto a cada servicio y maneja invocación entre servicios, estado y mensajería sin que el código de la app conozca la infraestructura de abajo. En Container Apps, la plataforma administra ese sidecar por vos: lo inyecta, lo actualiza y lo versiona sin que instales nada manualmente.
¿Cuánto cuesta una arquitectura con Front Door, API Management y Container Apps?
Las capas delante del código (Front Door Premium, API Management Standard v2 y el networking que las conecta) cuestan cerca de USD 1.730 por mes en West Europe. La API .NET detrás de esas capas suma entre USD 20 y USD 140 por mes según el tráfico, de 1 millón a 100 millones de requests.
¿Por qué fallan los health checks al usar Aspire con Container Apps en producción?
Fallan porque ServiceDefaults de Aspire solo mapea /health y /alive cuando el ambiente es Development, mientras que Container Apps en producción corre como Production y Terraform sondea rutas distintas, /healthz/live y /healthz/ready. El resultado es un 404 que falla ambas sondas y deja la revisión sin estado healthy.
¿Cómo se autentican los servicios entre sí sin usar claves en esta arquitectura?
Todo el camino usa identidades administradas: API Management obtiene un token con su identidad asignada por el sistema, los componentes de Dapr se autentican contra Cosmos DB y Service Bus con el azureClientId de una identidad asignada por el usuario, y los sidecars de Dapr se comunican entre sí con mTLS mutuo automático. La única excepción es el agente de OpenTelemetry, que usa connection string.
¿Qué hace Terraform y qué hace Aspire en un proyecto con Dapr?
Aspire maneja el entorno de desarrollo local: arranca servicios, sidecars y un almacén de estado con un F5 y nunca se despliega en Azure. Terraform define la infraestructura real que corre en producción (VNet, identidades, permisos y componentes de Dapr), aunque en esta serie esa definición de Terraform solo cubre un servicio, no la arquitectura completa.
Conclusión
Lo que deja esta serie completa no es una receta para copiar y pegar, es un mapa de dónde se rompen las costuras entre partes que se escribieron por separado. El stack funciona, las identidades administradas cubren seis de los siete saltos, y el costo de USD 1.730 por mes antes del código confirma que esta arquitectura se justifica por seguridad de red y gobernanza, no por ahorro de cómputo.
Si estás evaluando algo parecido para tu equipo, el criterio práctico es simple: antes de copiar un fragmento de Terraform o de C# de cualquier entrega de la serie, revisá contra qué nombres de componentes y qué rutas de health check está probado ese fragmento en particular, porque la serie misma demuestra que nombres y rutas cambiaron entre partes sin que nadie los cruzara. Esa revisión manual, hoy, es la única forma de evitar la falla silenciosa de los health checks antes de que llegue a producción.






