Azure
Connect an Azure service principal and run services on Container Apps with Azure Database for PostgreSQL and MySQL and Azure Managed Redis.
Loomplane uploads the approved commit to a container registry in your subscription, ACR Tasks builds the service there, and a container app runs the image. Nothing from your repository runs on a Loomplane machine.
Connect Azure
You connect a service principal: an application registered in Microsoft Entra ID, with a client secret.
In the Azure portal, open Microsoft Entra ID, then App registrations, and register an application. Note its Application (client) ID and Directory (tenant) ID.
Under Certificates & secrets, add a client secret and copy its value. Azure shows it once.
On the subscription you'll deploy to (or on the resource group, if you'd rather keep Loomplane inside one), open Access control (IAM) and give the application the Contributor role.
In Loomplane, open Integrations, choose Microsoft Azure, paste the secret, and fill in the tenant ID, client ID and subscription ID. The resource group is optional and defaults to loomplane.
If you prefer the CLI, az ad sp create-for-rbac --role Contributor --scopes /subscriptions/<id> does the first three steps at once. Its appId is the client ID, password the secret and tenant the tenant ID.
Loomplane signs in as the principal before it keeps the secret. A wrong or expired secret is refused with Entra ID's own AADSTS code, so you can tell a mistyped secret from an expired one. Then it asks Azure what the principal's role assignments allow in the resource group (or in the subscription while the group isn't there yet), and lists every action a deployment needs that they leave out. Contributor has them all.
How apps pull images
Contributor can't assign roles, and that decides how your apps pull their images. Give the principal Owner on the resource group, or Role Based Access Control Administrator there as well as Contributor, and each app pulls from the registry as a managed identity called loomplane-pull, which holds the AcrPull role on that registry and nothing else. With Contributor alone, apps pull with the registry's admin user, and its password goes into each app as a secret. The connection shows which you get under registry_pull, either identity or admin. Every deployment asks Azure again, so a role you add later counts from the next one. Check the connection again to update what it shows.
Azure takes a while to let a new identity pull. The first deployment after you grant the role can sit at "Waiting for the pull identity's role to reach the container registry" for a few minutes. Loomplane waits up to ten. If that runs out, retry the deployment.
Resource providers
The subscription needs Microsoft.App and Microsoft.ContainerRegistry. Loomplane registers them on the first deployment if you haven't, which Contributor on the subscription may do. With Contributor on only a resource group, register them yourself under the subscription's Resource providers. Apps that pull with the identity also need Microsoft.ManagedIdentity, which Loomplane registers when it first makes loomplane-pull.
The subscription and resource group are the connection's account. You can replace the secret, or switch to another principal with access to the same group, and nothing that uses the connection has to change.
Use it in a manifest
version: 1
defaults:
provider: azure-container-apps
region: westeurope
services:
api:
source: services/apiOr make the subscription a target in one of your Clouds and name it with cloud_target. See Cloud targets.
The regions are eastus, eastus2, centralus, westus2, westus3, canadacentral, brazilsouth, northeurope, westeurope, uksouth, francecentral, germanywestcentral, swedencentral, japaneast, koreacentral, southeastasia, centralindia and australiaeast. Leave region out and you get eastus.
What gets made
Everything lives in the connection's resource group, which Loomplane creates in the first region you deploy to if it isn't there.
- One Basic container registry, named from a hash of the subscription and resource group, since registry names are global across Azure. Its admin user stays on even when your apps pull with the identity.
- The
loomplane-pullmanaged identity, when the principal may assign roles. - One Container Apps environment per region, called
loomplane-<region>. It's consumption only, with no Log Analytics workspace. - A container app for each service in each environment, named after the service, the environment and the project.
A service with a Dockerfile builds from it. Anything else goes through Google's buildpacks, run by pack in the same ACR Tasks run. Each deployment builds the approved commit, tagged with its first twelve characters, and the app runs that image in a single active revision.
A public service takes HTTPS traffic from anywhere. A private one only takes traffic from apps in its own Container Apps environment, which means the same connection and the same region. The plan stops with a finding if a service that needs one sits in another region or account, and a service on another cloud can't reach it at all.
size picks the smallest consumption size that fits, a quarter CPU at a time with twice as many GiB of memory, up to 4 CPUs and 8 GiB. scale sets the minimum and maximum replicas. Without it Container Apps scales with traffic, down to zero.
A worker runs as a container app with no ingress. Nothing calls a worker to wake it, so it never scales to zero: the minimum is one replica even if you set scale.min to 0. A static site also runs as a container app, so it needs a Dockerfile that builds the site and serves it with nginx or any other web server. Without a Dockerfile the plan stops with a finding. The app takes traffic on the port you set, or the one your Dockerfile exposes, otherwise 8080 (the plan warns when it has to guess). A server that listens on 80, like nginx, needs EXPOSE 80 in the Dockerfile or port: 80 in the manifest.
Secret variables go in as app secrets, which Azure never shows again. Removing a service from the manifest deletes its container app. The registry and the environment stay for the group's other apps.
Databases
| Manifest kind | Made on Azure | Provider | Versions |
|---|---|---|---|
postgres | Azure Database for PostgreSQL flexible server | azure-database | 17, 16, 15 |
mysql | Azure Database for MySQL flexible server | azure-database | 8.4, 8.0.21 |
redis | Azure Managed Redis | azure-managed-redis | Azure's current one |
resources:
db:
kind: postgres
version: "16"
provider: azure-database
region: westeurope
cache:
kind: redis
provider: azure-managed-redis
region: westeuropeLeave version out and you get Postgres 17 or MySQL 8.4. Each database goes in the connection's resource group. A service that needs one has to run on Azure in the same account, or the plan stops with a finding. It gets the address as an app secret.
Postgres and MySQL run on one Standard_B1ms Burstable server, with 32 GiB of storage for Postgres and 20 GiB for MySQL. The admin user is loomplane, and its password is 32 random characters Loomplane makes the first time it applies the database and keeps in Vault. Each server has public network access and one firewall rule, AllowAllAzureServicesAndResourcesWithinAzureIps. That rule is how your container apps get in, and it also lets in traffic from every other Azure customer's resources, in any subscription. Only the password and TLS stand between them and your data. Addresses ask for TLS (sslmode=require for Postgres, tls=true for MySQL).
Redis is an Azure Managed Redis cluster on the Balanced_B0 size with one database called default. It only takes TLS connections on port 10000 and authenticates with an access key, so the address looks like rediss://:<key>@<host>:10000. It uses Azure's enterprise clustering policy, which gives you a single endpoint, so a client that doesn't speak Redis Cluster works fine. Loomplane reads the key when it applies the cache and keeps the address as a secret. If you regenerate the key in Azure your apps keep the old one and can't connect until the cache is applied again, so don't rotate the key there. Loomplane sets no network rule on the cluster, so its endpoint takes connections from anywhere that has the key. Managed Redis isn't offered in every region. If yours lacks it, the deployment fails with a message that names the region and suggests another.
Server and cluster names are global DNS names across Azure. Each is built from the resource, the environment and the project, and ends in a hyphen and six hex characters of a hash of your subscription ID.
The databases need Microsoft.DBforPostgreSQL, Microsoft.DBforMySQL and Microsoft.Cache. Loomplane registers each on the first deployment that makes that kind of database. With Contributor on only a resource group, register them yourself.
Removing a database from the manifest deletes it with its data, so take a backup first.
What it can't do yet
Databases only have public endpoints for now, and VNet integration and private endpoints aren't there. Every database gets the smallest size, with no way to pick another. Build logs are under Services, Tasks, Runs in the container registry, and Loomplane shows the deployment's progress.