Azure Module Dependencies
The Merkalis Azure modules are designed for provisioning the components of a complete Kastoria environment. This page records which module outputs are consumed as inputs by which other modules, so that wiring and build order are explicit rather than implicit.
Module inventory
| Module | Role |
|---|---|
| networking | Provisions the VNet and its subnets (compute / storage / gateway) |
| secrets | Provisions a Key Vault and a reader user-assigned identity |
| filestore | Provisions SMB file shares, one storage account per shard |
| objectstore | Provisions Blob + table storage, one storage account per shard |
| queue | Provisions a Storage Queue on a dedicated key-disabled account (optional) |
| compute | Provisions the Container App Environment with container apps and jobs |
| gateway | Provisions the reverse proxy entrypoint, with optional Cloudflare Tunnel and mTLS proxy |
| telemetry | Provisions the OTel collector container app and Grafana datasource |
An environment also defines a user-assigned storage-accessor identity that no module produces; it feeds the storage and compute modules (see Storage identity).
Dependency graph
graph TD
RG[Resource Group]
ID[User Managed Identity<br/>storage accessor]
NET[networking]
SEC[secrets]
FILE[filestore]
OBJ[objectstore]
COMP[compute]
GW[gateway]
TEL[telemetry]
QUEUE[queue]
RG --> NET & SEC & FILE & OBJ & COMP & GW & TEL & QUEUE
ID --> FILE & OBJ & COMP
NET -->|subnet ids| SEC & FILE & OBJ & COMP & QUEUE
QUEUE -->|storage_account_name, queue_name, publisher_id, publisher_client_id| COMP
SEC -->|vault_uri, identity_id| COMP
SEC -->|vault_id, vault_name, identity_id| GW
FILE -->|file_shares_config, access keys| COMP
COMP -->|environment id, default domain, identity_id| GW
COMP -->|environment id, identity_id| TEL
Output → input edges
networking
| Output | Consumer | Input | Notes |
|---|---|---|---|
created_subnets["compute"].id |
secrets | allowed_subnet_ids |
Key Vault service-endpoint ACL |
| filestore | allowed_subnet_ids |
shared with storage |
|
| objectstore | allowed_subnet_ids |
shared with storage |
|
| queue | allowed_subnet_ids |
the only subnet the queue admits | |
| compute | subnet_id |
ACA VNet integration | |
created_subnets["storage"].id |
filestore | allowed_subnet_ids |
|
| objectstore | allowed_subnet_ids |
||
created_subnets["gateway"].id |
compute | gateway_subnet_id |
Subnet names are not invented by the module — the caller declares the set of
subnets, and networking mirrors them back in created_subnets, keyed by
subnet name (compute / storage / gateway in the standard layout).
secrets
| Output | Consumer | Input | Notes |
|---|---|---|---|
vault_uri |
compute | key_vault_uri |
for key_vault_secrets per app/job |
identity_id |
compute | key_vault_reader_identity_id |
reader identity attached to apps/jobs reading Key Vault |
| gateway | user_assigned_identity_id |
proxy reads the vault for the CA cert + tunnel token | |
vault_id |
gateway | key_vault_id |
|
vault_name |
gateway | key_vault_name |
filestore
| Output | Consumer | Input | Notes |
|---|---|---|---|
file_shares_config |
compute | apps[].share_mounts, jobs[].share_mounts |
non-sensitive share metadata |
file_shares[].access_key |
compute | storage_access_keys |
delivered through a separate sensitive map |
The two paths are deliberately separate: the non-sensitive file_shares_config
drives for_each on apps/jobs, while the sensitive access_key values travel
through the dedicated storage_access_keys map so they do not taint for_each
keys downstream.
Note: These outputs are only indirectly consumed by compute
queue
| Output | Consumer | Input | Notes |
|---|---|---|---|
storage_account_name |
compute | app_config.queue_writer |
accountName in the JSON-encoded queueWriter section |
queue_name |
compute | app_config.queue_writer |
queueName in the same section |
publisher_id |
compute | apps[].user_managed_queue_app.id, jobs[].user_managed_queue_app.id |
attached to the workloads that publish |
publisher_client_id |
compute | same, and app_config.queue_writer |
managedIdentityClientId in the queueWriter section |
publisher_principal_id |
— | — | the Sender grant target, for post-apply inspection |
queue_url |
— | — | handed to your queue reader |
storage_account_id |
— | — | currently unconsumed |
The queue module owns its publisher identity end to end — it creates it,
grants it Storage Queue Data Message Sender, and exports it — the same shape
secrets uses for its reader identity. You decide which workloads receive it,
just as you decide which receive the storage-accessor identity. The queue is
optional: omit the module and leave queue_writer unset.
Note: These outputs are only indirectly consumed by compute
compute
| Output | Consumer | Input | Notes |
|---|---|---|---|
container_app_environment_id |
gateway | container_app_environment_id |
proxy provisions into the same ACA environment |
| telemetry | container_app_environment_id |
OTel collector provisions into the same environment | |
container_app_environment_default_domain |
gateway | domain |
overridable per environment |
identity_id |
gateway | registry_identity_id |
ACR pull auth for proxy images |
| telemetry | registry_identity_id |
ACR pull auth for the collector image | |
identity_principal_id |
— | — | consumed at the environment level for ACR RBAC |
app_fqdns |
— | — | currently unconsumed |
compute depends almost entirely on its own configuration rather than on
module outputs: the caller supplies apps, jobs, storage_access_keys,
app_config, key_vault_uri, key_vault_reader_identity_id, and the subnet
IDs.
Storage identity
The environment defines one user-assigned managed identity that is not owned by any module. It is the identity Kastoria uses to reach storage without account keys, and it feeds three places:
filestore.user_managed_identity_principal_id→ share-access role assignmentobjectstore.user_managed_identity_principal_id→ blob + table data contributor role assignmentscomputeapps/jobs asuser_managed_storage_app, so workloads authenticate to storage with the identity rather than connection strings
It does not feed queue: holding the storage identity grants no access to the
queue. A workload that publishes carries the queue module's publisher identity
as well (see queue).
Leaf modules
objectstore, gateway, and telemetry produce no inputs to other modules in
the composition:
| Module | Outputs | Consumed by |
|---|---|---|
objectstore |
storage_accounts, tables |
environment-side wiring only — workload access is via RBAC + app_config.storage_tiers |
gateway |
container_app_*, cloudflared_*, cloudflare_tunnel_*, mtls_proxy_* |
nothing — terminal |
telemetry |
grafana_datasource_uid |
nothing — terminal |
Ordering
Terraform/OpenTofu derives apply order from references, but the effective levels are:
- Resource group + storage-accessor identity
networkingsecrets,filestore,objectstore,queue(in parallel)compute(needs the subnets, vault URI/reader identity, share metadata + keys, and the queue account, name and publisher identity whenqueue_writeris set)gateway,telemetry(need the Container App Environment id)
On a fresh subscription, also declare an explicit depends_on from compute
to the Microsoft.App resource-provider registration and the storage-accessor
identity — neither is a data reference, so without them the ordering would be
implicit and racy.