CI/CD de Logic Apps Standard en ASE privado sin VNet agent
En pocas palabras: Sí. Se logra con Run-From-Package apuntando a una URL de blob autenticada por managed identity: la Logic App descarga el paquete desde dentro de la VNet con su propia identidad y el pipeline nunca toca Kudu ni maneja SAS tokens. Vicky documentó el método con seis errores reales.
¿Se puede hacer CI/CD de Logic Apps Standard en un App Service Environment privado (ILB) sin exponer el SCM ni manejar SAS tokens? Sí. La receta es Run-From-Package con una URL de blob autenticada por managed identity: la app descarga el paquete desde adentro de la VNet con su propia identidad, y el pipeline nunca toca Kudu.
Si alguna vez intentaste desplegar una Logic App Standard sobre un ASE aislado y te chocaste con que el zip-deploy simplemente no llega, sabés de qué hablo. Este artículo desarma el enfoque de CI/CD para Logic Apps Azure Standard que documentó Vicky en dev.to, con los seis errores reales que se comieron en el camino.
CI/CD para Logic Apps Standard es el proceso de compilar, validar y desplegar flujos de trabajo de Azure Logic Apps en su plan Standard (single-tenant, corriendo sobre App Service) mediante un pipeline automatizado. En un ASE privado tipo ILB, el endpoint SCM (Kudu) no tiene cara pública, así que el despliegue se resuelve con Run-From-Package apuntando a un blob y una identidad gestionada, sin SAS ni acceso a Kudu.
En 30 segundos
- El problema: un ILB ASE v3 no expone SCM/Kudu, así que zip-deploy desde un agente hosteado y el publish desde VS Code fallan de entrada.
- La solución: Run-From-Package con URL de blob autenticada por managed identity. Sin SAS token, sin expiración, sin tocar SCM.
- Los dos app settings clave:
WEBSITE_RUN_FROM_PACKAGE(URL sin query string) yWEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID=SystemAssigned. - Tres role assignments: la identidad del pipeline necesita Storage Blob Data Contributor + Website Contributor; la identidad de la app necesita Storage Blob Data Reader.
- Ojo con la propagación de RBAC: tarda ~5 minutos. Reiniciar la app a los 30 segundos te da un falso error.
¿Por qué falla el despliegue normal en un ASE privado (ILB)?
Falla porque un ILB App Service Environment v3 no tiene endpoint SCM (Kudu) público, y las dos rutas habituales de despliegue dependen justo de eso. El zip-deploy desde un agente hosteado por Microsoft le habla al SCM, que es privado. El publish desde VS Code o el portal arrastra la misma dependencia (y encima no es CI/CD).
La respuesta clásica es levantar un agente self-hosted adentro de la VNet. Es la solución correcta a largo plazo, pero mientras tanto podés desplegar sin eso. Con Run-From-Package el pipeline solo le habla a dos planos públicos: ARM (management.azure.com) para setear un app setting y reiniciar, y Blob Storage para subir el paquete. La app después se baja el zip sola, desde adentro de la VNet, con su propia identidad. SCM no se toca nunca. Lo explicamos a fondo en comparativa entre Microsoft y GitHub.
Seamos honestos con una cosa: esto no es “sin agente”. Necesitás un agente (hosteado o self-hosted) que llegue a ARM y a Storage. Lo que evitás es el SAS token y la dependencia de SCM, que no es poco cuando el ASE está aislado.
¿Qué roles RBAC y managed identities necesito antes de arrancar?
Necesitás tres asignaciones de rol: dos para la identidad del pipeline y una para la identidad de la propia Logic App. Sin las tres, el despliegue arranca pero la app no puede leer el paquete y muere en silencio.
- Identidad del pipeline → Storage Blob Data Contributor: sobre la cuenta de storage, para poder subir el zip con
--auth-mode login. - Identidad del pipeline → Website Contributor: sobre el resource group de la Logic App, para setear app settings y reiniciar.
- Identidad de la Logic App → Storage Blob Data Reader: sobre la cuenta de storage. Esta es la llave del despliegue sin SAS: la app se baja el paquete con su system-assigned identity.
¿No te acordás el object ID de la identidad del pipeline? Pasa siempre. Corré un pipeline descartable en el pool destino que le pregunte al Instance Metadata Service (http://169.254.169.254/metadata/identity/...), decodificá el token y sacá el oid. En el caso de la fuente, la service connection de tipo Managed Identity existía hacía meses pero nunca había funcionado, porque la system-assigned identity del VM del agente estaba apagada. Detalle nada menor.
¿Cómo se estructura el repositorio?
Una carpeta por recurso Logic App Standard, y adentro un subfolder por workflow. El zip que se despliega es el contenido de esa carpeta. Archivos críticos en la raíz de la app: host.json (runtime + extension bundle Microsoft.Azure.Functions.ExtensionBundle.Workflows), connections.json y parameters.json (arrancan en {}), más el .funcignore. Cada workflow es su propio workflow.json. Te puede servir nuestra cobertura de certificaciones Microsoft Azure requeridas.
Un consejo que vale oro: empezá con un workflow piloto trivial (un “Heartbeat” sin conectores ni secretos). Probás la cañería primero, migrás los workflows reales después. Y ojo con los paths: si tus archivos viven en un subfolder del repo (por ejemplo myrepo/logicapps-platform/apps/...), las variables y triggers del YAML tienen que incluir ese prefijo, o te vas a comer un Cannot find path ...\apps\.
¿Cómo queda el pipeline de CI/CD para Logic Apps Azure Standard?
El pipeline sigue seis pasos, todos en PowerShell si el agente es Windows (o bash + vmImage: ubuntu-latest si la service connection usa service principal). La lógica es siempre la misma.
- Validar JSON y bloquear secretos inline: recorrés los
.json, parseás cada uno y frenás el build si detectás un"password"o"clientSecret"hardcodeado. - Armar el ZIP con
includeRootFolder: false:host.jsontiene que quedar en la raíz del zip, no anidado un nivel más abajo. - Subir el paquete al blob:
az storage blob upload --auth-mode login --overwrite. Sin key, sin SAS. - Setear los app settings:
WEBSITE_RUN_FROM_PACKAGEcon la URL del blob yWEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID=SystemAssigned. - Reiniciar la app:
az webapp restart. Recién ahí la app se baja el paquete nuevo. - Verificar el workflow: un GET REST a
.../workflows?api-version=2022-03-01que imprima los nombres. Un pipeline en verde no alcanza.
Si todo esto te suena a que estás armando infraestructura seria sobre la nube, tenelo en cuenta a la hora de elegir dónde corren tus sitios y servicios: para hosting y dominios en Argentina, donweb.com te resuelve la parte de infraestructura web mientras Azure se queda con la orquestación de integraciones.
¿Cuáles son los dos app settings que casi todos olvidan?
Los dos settings son WEBSITE_RUN_FROM_PACKAGE y WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID, y el segundo es el que se salta casi todo el mundo. El primero es la URL del blob, en texto plano, sin query string de SAS. El segundo, seteado en SystemAssigned, le dice a App Service que busque el paquete usando la managed identity de la app.
¿Qué pasa si te olvidás el segundo? El runtime intenta acceso anónimo al blob, falla, y el portal te tira Error retrieving workflows. Encountered an error (ServiceUnavailable) from host runtime. Lo peor es que el pipeline queda en verde igual. Este es, según la propia fuente, “el paso que más se olvida”. Más contexto en seguridad en repositorios GitHub.
Errores comunes en la implementación (y cómo salir)
Estos son los tropezones reales que documentó la fuente, en orden. Si estás debuggeando, escaneá esta lista primero.
Unable to locate executable file: 'bash': asumieron un agente Linux, pero era Windows self-hosted. Solución: todos los pasos a PowerShell (pwsh).Identity not founden el metadata endpoint: la system-assigned identity del VM del agente estaba apagada. Prendela (VM → Identity → System assigned → On); si la activaste con el VM ya booteado, capaz haya que reiniciarlo para que responda el endpoint del token.Azure CLI 2.x is not installed: instalá el CLI en el agente y reiniciá el servicio del agente para que tome el nuevo PATH.Cannot find path '...\apps\': los archivos estaban un nivel más profundo de lo que esperaba el YAML. Ajustá las variables para incluir el prefijo.--expiry should be within 7 days: el SAS basado en identidad (user-delegation) topea a 7 días. Un SAS de 7 días es una bomba de tiempo igual, porque la app relee la URL en cada reinicio. Solución: sacar el SAS del medio y pasar al enfoque de managed identity.
Cuando el runtime no arranca, tenés un diagnóstico de 60 segundos con az cli que chequea los cuatro sospechosos de una: ¿está prendida la identity de la app?, ¿están los dos settings WEBSITE_RUN_FROM*?, ¿tiene la identity de la app un rol sobre el storage?, ¿es alcanzable el storage (publicNetworkAccess)? El que vuelva vacío es tu culpable.
¿Cómo confirmo que el despliegue salió bien de verdad?
Un pipeline en verde no garantiza nada. La confirmación real tiene dos niveles: que el workflow cargue y que el workflow ejecute. Un workflow que se carga pero nunca corre no es un despliegue exitoso.
- El workflow carga: el GET REST a
/workflowsimprime el o los nombres. Si el portal muestraServiceUnavailable, la app no pudo bajar el paquete. - El workflow ejecuta: disparás un run de prueba y verificás que produjo el output esperado.
- Los settings están:
az webapp config appsettings listte muestra los dosWEBSITE_RUN_FROM*. - La identity tiene su rol:
az role assignment listconfirma el Storage Blob Data Reader sobre el storage.
Comparativa: SAS vs. agente en VNet vs. Run-From-Package con MI
Las tres opciones funcionan, pero envejecen distinto. La del SAS es la que más “sorpresas” reparte a futuro. Ya lo cubrimos antes en diferencias entre Windows 11 y Ubuntu.
| Enfoque | Secretos | Toca SCM | Punto débil |
|---|---|---|---|
| Run-From-Package con SAS | Sí (SAS) | No | SAS user-delegation topea a 7 días; la app muere callada cuando expira, en el reinicio que le toque |
| Zip-deploy con agente self-hosted en VNet | No | Sí | El despliegue queda rehén de la salud de un VM; overhead de mantener el agente |
| Run-From-Package con managed identity | No | No | Requiere prender identities y esperar ~5 min de propagación RBAC |

¿Cuándo elegir cada una? Si necesitás soporte 100% oficial y ya tenés VMs en la VNet, el agente self-hosted es el camino clásico. Si querés cero secretos, cero expiración y desplegar desde cualquier agente que llegue a ARM y Storage, la managed identity gana. El SAS lo dejaría para nada: es la que peor escala en el tiempo.
Preguntas Frecuentes
¿Puedo usar Managed Identity en lugar de SAS tokens para desplegar a blob storage?
Sí, y es lo recomendable. La Logic App usa su system-assigned identity con el rol Storage Blob Data Reader para bajarse el paquete desde el blob, sin ningún SAS. El pipeline sube el zip con az storage blob upload --auth-mode login, autenticándose también por identidad. Así no existe ningún token que pueda expirar ni ninguna key que administrar.
¿Qué es WEBSITE_RUN_FROM_PACKAGE y cómo lo configuro?
WEBSITE_RUN_FROM_PACKAGE es el app setting que le indica a App Service que corra la app desde un paquete zip en lugar del contenido del filesystem. Lo configurás con la URL del blob (sin query string de SAS) vía az webapp config appsettings set. Para que la descarga funcione con identidad, sumá WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID=SystemAssigned y reiniciá la app.
¿Cuáles son los requisitos de RBAC para desplegar Logic Apps vía DevOps?
Tres asignaciones. La identidad del pipeline necesita Storage Blob Data Contributor sobre la cuenta de storage y Website Contributor sobre el resource group de la Logic App. La identidad de la Logic App necesita Storage Blob Data Reader sobre la cuenta de storage. Dejá pasar unos 5 minutos tras crear cada rol antes de probar.
¿Por qué el portal muestra ServiceUnavailable si el pipeline quedó en verde?
Casi siempre es porque falta el setting WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID=SystemAssigned o porque la identity de la app todavía no tiene el rol Storage Blob Data Reader. Sin eso, el runtime intenta acceso anónimo al blob y falla con ServiceUnavailable. Sumá el setting, otorgá el rol, esperá la propagación de RBAC y reiniciá.
¿Este método sirve también para un ASE con endpoint privado en el storage?
Sí, siempre que la cuenta de storage sea alcanzable desde el ASE. La ruta simple es habilitar acceso de red pública, pero un private endpoint también funciona. En ese caso vas a necesitar la resolución DNS correspondiente para que la app resuelva el nombre del blob desde adentro de la VNet.
Conclusión
Lo que cambia acá es el punto de dolor: en un ILB ASE ya no dependés del SCM ni de repartir SAS tokens que expiran en el peor momento. El paquete es inmutable por build, la app se lo baja con su propia identidad, y el pipeline solo necesita llegar a ARM y a Storage.
Si estás por armar CI/CD para Logic Apps Azure Standard sobre un ASE aislado, el orden práctico es claro: prendé las dos managed identities, asigná los tres roles, esperá los cinco minutos de RBAC, y no te olvides del segundo app setting. Probá primero con un workflow piloto trivial, confirmá que ejecuta (no solo que carga), y recién ahí migrá lo real. El pipeline en verde es el principio de la verificación, no el final.
Fuentes
- Microsoft Learn – Despliegue DevOps para Logic Apps single-tenant (documentación oficial)
- dev.to – CI/CD for Azure Logic Apps Standard on a Private (ILB) ASE Without a VNet Agent
- Microsoft Tech Community – Deploying Logic Apps Standard con Managed Identity y private networking
- BizTalkers – ZipDeploy vs Run-From-Package explicado
- GitHub Azure/logicapps – Discusión sobre despliegue en ASE privado






