Skip to content

About

Bicep modules for deploying GoRules on Azure Container Apps

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GoRules Bicep Modules

Bicep modules for deploying GoRules on Azure Container Apps.

What is GoRules?

GoRules is a Business Rules Management System (BRMS) for managing and executing business rules. It has two components:

  • BRMS - Management UI and API for creating, editing and publishing rules. Requires a database and blob storage.
  • Agent - Stateless rule execution engine that pulls published rules from blob storage. Scales independently from BRMS.

Architecture

flowchart TB
    Internet((Internet))

    subgraph VNet["Virtual network"]
        subgraph Apps["apps subnet"]
            BRMS[BRMS]
            Agent[Agent]
        end

        subgraph Endpoints["private-endpoints subnet"]
            BlobPE[Blob]
            VaultPE[Key Vault]
            PostgresPE[PostgreSQL]
        end
    end

    Storage[(Storage account)]
    Vault[(Key Vault)]
    Postgres[(PostgreSQL<br/>flexible server)]

    Internet -->|HTTPS| BRMS
    Internet -->|HTTPS| Agent
    BRMS --> PostgresPE --> Postgres
    BRMS --> VaultPE --> Vault
    BRMS --> BlobPE
    Agent --> BlobPE --> Storage
Loading
  • BRMS and the Agent run in a Container Apps environment inside the virtual network. Each gets an HTTPS address under *.azurecontainerapps.io, so BRMS works without a custom domain.
  • PostgreSQL, Key Vault and storage are private, reached through private endpoints.
  • The apps use managed identities, not passwords or keys. BRMS signs in to PostgreSQL with Microsoft Entra ID, reads Key Vault and writes to storage. The Agent reads storage and its own Key Vault secrets.
  • Logs go to a Log Analytics workspace.

Deployment patterns

Pattern Components Example
Full stack BRMS + Agent + PostgreSQL + Key Vault + storage full-stack
Agent only Agent + storage agent-only
Internal Full stack, reachable only from the virtual network internal
Existing virtual network Full stack in your own subnets existing-vnet
Existing environment Agent in the environment of another deployment existing-environment

Requirements

Name Version
Azure CLI >= 2.61
Bicep CLI >= 0.48.1
GoRules Agent >= 2.0.2

The deployer needs Owner on the resource group. With database.deletionProtection off, Contributor plus Role Based Access Control Administrator is enough. A private registry also needs role assignment rights on the registry.

Quick start

az group create --name rg-gorules-prod --location swedencentral

export GORULES_LICENSE_KEY=...

az stack group create \
  --name gorules \
  --resource-group rg-gorules-prod \
  --template-file main.bicep \
  --parameters examples/full-stack/main.bicepparam \
  --action-on-unmanage deleteAll \
  --deny-settings-mode none

The brmsUrl and agentUrl outputs are the HTTPS addresses. To preview changes, run az deployment group what-if with the same template and parameters. It skips the Key Vault resources because they take secure parameters.

To remove everything the stack created, first delete the database lock if deletion protection is on:

az lock delete --name do-not-delete --resource-group rg-gorules-prod \
  --resource-type Microsoft.DBforPostgreSQL/flexibleServers --resource-name <server>

az stack group delete --name gorules --resource-group rg-gorules-prod --action-on-unmanage deleteAll

Parameters

Name Default Description
projectName required Used in resource names. Together with environmentName at most 25 characters.
environmentName required Used in resource names, e.g. dev, prod.
location resource group location Azure region.
tags {} Merged with Project, Environment and ManagedBy.
zoneRedundant true Spreads the Container Apps environment, storage, the NAT gateway and a high-availability database across availability zones. Set false in regions without zones. Can't be changed later.
network.addressPrefix 10.0.0.0/16 Virtual network range, /22 or larger.
network.natGateway false Sends outbound traffic through a NAT gateway with a static public IP. With zoneRedundant on, not available in India South Central.
network.internal false Makes BRMS and the Agent reachable only from the virtual network. See Internal environment. Can't be changed later.
network.appsSubnetId none Existing subnet for Container Apps. See Existing virtual network. Can't be changed later.
network.privateEndpointSubnetId none Existing subnet for the private endpoints.
network.privateDnsZoneIds none Existing private DNS zones for the private endpoints.
existingEnvironment none Existing Container Apps environment to deploy into, { id, workspaceId }. See Existing environment.
storage.versioning true Keeps previous versions of overwritten blobs.
containerRegistryId none Azure Container Registry to pull images from with managed identity.
alertActionGroupId none Action group that gets alert notifications. See Alerts.
brms none BRMS settings, see below. Omit to skip BRMS.
agent none Agent settings, see below. Omit to skip the Agent.
database {} PostgreSQL settings for BRMS, see below.
vault {} Key Vault settings, see below.
licenseKey required with BRMS GoRules license key. Secure.
licenseKeySecretId none The license key from your own Key Vault, instead of licenseKey. See Existing Key Vault secrets.
aiApiKey required with brms.ai API key for the AI provider. Secure.
aiApiKeySecretId none The API key from your own Key Vault, instead of aiApiKey.
brmsSecrets {} Extra secret environment variables for BRMS, e.g. { SSO_OAUTH2_CLIENT_SECRET: '...' }. Stored in Key Vault. Secure.
brmsSecretIds {} Like brmsSecrets, from your own Key Vault.
agentSecrets {} Extra secret environment variables for the Agent, e.g. { RELEASE_ZIP_PASSWORD: '...' }. Stored in Key Vault. Secure.
agentSecretIds {} Like agentSecrets, from your own Key Vault.

BRMS and Agent

Both take the same settings, plus the BRMS-only ones marked below.

Name Default Description
image docker.io/gorules/brms:latest, docker.io/gorules/agent:latest Container image.
cpu 1 (BRMS), 0.5 (Agent) vCPU per replica, 0.25 to 4 in steps of 0.25. Memory is twice the vCPU in GiB.
minReplicas 1 Use 2 or more with zone redundancy.
maxReplicas required Up to 1000.
cpuTarget 60 Average CPU percentage that triggers scaling.
allowedCidrBlocks required Client ranges allowed in, e.g. ["0.0.0.0/0"].
domain none Custom domain. See Custom domains.
certificateId none Key Vault certificate for domain. Omit for a free managed certificate.
env [] Extra environment variables, [{ name, value }].
secretsProvider env BRMS only. See Secrets encryption.
ai none BRMS only. See AI assistant.

Database

Name Default Description
version 17 PostgreSQL major version: 16, 17 or 18.
sku Standard_B2s Compute size. Burstable sizes (Standard_B*) suit development; use General Purpose (Standard_D2ds_v5 and up) for production.
storageSizeGB 32 Grows automatically. Redeploys keep the grown size.
highAvailability false Standby replica, zone redundant when zoneRedundant is on. Not available on Burstable sizes.
backupRetentionDays 7 7 to 35.
geoRedundantBackup false Copies backups to the paired region. Can't be changed later.
deletionProtection true Locks the server against deletion.

Key Vault

Agent-only deployments get a Key Vault only when agentSecrets is set.

Name Default Description
softDeleteRetentionDays 90 Days a deleted vault and its secrets can be recovered, 7 to 90. Can't be changed later.
purgeProtection true Blocks permanent deletion during the retention period. Can't be turned off once on.

Internal environment

param network = {
  internal: true
}

BRMS and the Agent then get a private IP in the virtual network and no public address. They are reachable from the virtual network and from networks connected to it by peering, VPN or ExpressRoute. Set allowedCidrBlocks to those private ranges.

The template creates a private DNS zone for the environment's default domain and links it to the virtual network, so brmsUrl and agentUrl resolve there. Other networks need the same names. Link the zone to them or point a wildcard record for the domain at the environmentIp output in your own DNS.

The apps keep HTTPS on the default domain. Outbound traffic still reaches the internet, which BRMS needs for its license check.

Existing virtual network

param network = {
  appsSubnetId: '/subscriptions/.../virtualNetworks/vnet-prod/subnets/gorules-apps'
  privateEndpointSubnetId: '/subscriptions/.../virtualNetworks/vnet-prod/subnets/gorules-endpoints'
}
  • Both subnets must be in the same virtual network, in the subscription and region of the deployment.
  • The apps subnet is only for Container Apps. Delegate it to Microsoft.App/environments and make it /27 or larger. A /27 runs up to 90 replicas.
  • Routes and network security groups on the subnets must let the apps reach the internet and Azure. BRMS checks its license online. See securing a virtual network for the rules Container Apps needs.
  • natGateway and addressPrefix don't apply. Outbound traffic follows your routes.
  • The deployer also needs Network Contributor on the virtual network.

The template creates private DNS zones for storage, Key Vault and PostgreSQL and links them to the virtual network. A virtual network can be linked to only one zone per name, so if yours already uses central privatelink zones, pass them instead:

privateDnsZoneIds: [
  '/subscriptions/.../privateDnsZones/privatelink.blob.core.windows.net'
  '/subscriptions/.../privateDnsZones/privatelink.vaultcore.azure.net'
  '/subscriptions/.../privateDnsZones/privatelink.postgres.database.azure.com'
]

The private endpoints then register in these zones. The PostgreSQL zone is only needed with BRMS. The deployer needs Private DNS Zone Contributor on the zones. Your DNS has to resolve them.

Existing environment

param existingEnvironment = {
  id: '/subscriptions/.../managedEnvironments/gorules-prod-env'
  workspaceId: '/subscriptions/.../workspaces/gorules-prod-logs'
}
param network = {
  privateEndpointSubnetId: '/subscriptions/.../virtualNetworks/gorules-prod-vnet/subnets/private-endpoints'
  privateDnsZoneIds: [
    '/subscriptions/.../privateDnsZones/privatelink.blob.core.windows.net'
    '/subscriptions/.../privateDnsZones/privatelink.vaultcore.azure.net'
  ]
}

Several deployments can share one Container Apps environment, e.g. an Agent per team next to a full stack. A deployment with existingEnvironment skips the virtual network, NAT gateway, DNS zones, Log Analytics and the environment. It keeps its own apps, identities, storage, Key Vault and alerts. Settings like allowedCidrBlocks and zoneRedundant still apply to it.

If the environment comes from this template, take the values from the environmentId, workspaceId, privateEndpointSubnetId and privateDnsZoneIds outputs of its deployment. Otherwise use the environment's Log Analytics workspace and a subnet and private DNS zones of its virtual network, as in Existing virtual network.

  • Deploy in the environment's region. It can be in another resource group.
  • The deployer needs Contributor on the environment and Role Based Access Control Administrator on the workspace.
  • domain gets a free managed certificate, so the environment must be external. certificateId can't be used.
  • With BRMS, privateDnsZoneIds also needs the PostgreSQL zone.
  • Delete these deployments before the one that owns the environment.

Custom domains

param brms = {
  // ...
  domain: 'brms.example.com'
}

BRMS and the Agent can each have a custom domain. Before setting domain, add two records at your DNS provider:

Type Name Value
CNAME brms.example.com The app's default host, e.g. gorules-prod-brms.<unique>.<region>.azurecontainerapps.io
TXT asuid.brms.example.com The customDomainVerificationId output

brmsUrl shows the default host until domain is set. Then set domain and deploy again. brmsUrl and the links BRMS sends switch to the domain.

Without certificateId, Container Apps issues a free certificate and renews it. The certificate authority checks the domain over the internet, so:

  • allowedCidrBlocks must include 0.0.0.0/0 and the environment can't be internal.
  • The CNAME must point straight at the app, not through a proxy or CDN.
  • The domain must be a subdomain like brms.example.com.
  • If the domain has CAA records, one of them must allow digicert.com.

The certificate takes about five minutes to issue. A domain moved from certificateId to a free certificate has no HTTPS until then. On later deployments what-if shows the certificate and its binding as changed. They stay as they are.

To use your own certificate, import it into Key Vault and set certificateId to the vault resource ID followed by /secrets/<certificate name>. The vault must use Azure RBAC. The template gives a <project>-<environment>-env identity Key Vault Secrets User on the certificate, so the deployer needs Owner or Role Based Access Control Administrator on the vault. Container Apps picks up a renewed certificate within 12 hours. If the vault blocks public access, the environment's virtual network needs a private endpoint to it. Without one the import fails with a permission error.

In an internal environment Azure doesn't check the DNS records. Point the domain at environmentIp in your DNS and use certificateId.

Existing Key Vault secrets

param licenseKeySecretId = '/subscriptions/.../vaults/kv-corp/secrets/gorules-license-key'
param brmsSecretIds = {
  SSO_OAUTH2_CLIENT_SECRET: '/subscriptions/.../vaults/kv-corp/secrets/sso-client-secret'
}

Secrets can stay in your own Key Vault. Pass the vault resource ID followed by /secrets/<secret name> instead of the value. The apps read the latest version from your vault and restart within 30 minutes of a new one. Values and IDs can be mixed, one or the other per variable. Moving a variable between a value and an ID doesn't start a new revision, so restart the app if the value differs.

The vault must use Azure RBAC. The template gives the BRMS or Agent identity Key Vault Secrets User on each secret, so the deployer needs Owner or Role Based Access Control Administrator on the vault. If the vault blocks public access, the environment's virtual network needs a private endpoint to it.

To copy a secret into the template's vault at deployment time instead, use az.getSecret() in the parameters file. Your vault then needs enabledForTemplateDeployment.

Secrets encryption

BRMS encrypts secrets stored in rules with a key that the template creates:

  • env (default): a random master key, generated once and stored in Key Vault.
  • azure-keyvault: an RSA key in Key Vault. The key never leaves the vault.

Caution

Never delete or rotate the key. Deleting the master key secret or the RSA key makes all BRMS secrets unreadable. BRMS always uses the newest RSA key version, so rotating it breaks secrets encrypted with an older one. Keep purge protection on in production.

Choose secretsProvider before first use. Switching it later makes existing BRMS secrets unreadable. The stack also deletes the old master key secret.

The cookie secret and master key are generated on the first deployment only. Later deployments keep them.

AI assistant

param brms = {
  // ...
  ai: {
    provider: 'anthropic'
    model: 'claude-sonnet-4-6'
  }
}
param aiApiKey = readEnvironmentVariable('ANTHROPIC_API_KEY')
Name Default Description
provider required openai, anthropic, google or azure-openai.
model required Model name. For azure-openai, the deployment name.
temperature 0.4 0 to 2, as a string.
maxOutputTokens 32000
thinkingLevel medium high or medium.
contextWindow none Overrides the context window size.
azureResourceName none Required for azure-openai.

Container Apps times out requests after 240 seconds, enough for slow first responses from large models.

Alerts

BRMS and the Agent get four alerts each, named after the app, e.g. gorules-prod-brms-cpu-high. Set alertActionGroupId to get notified. Without it the alerts still show in Azure Monitor.

Alert Fires when
cpu-high Average CPU is above 80% for 5 minutes.
memory-high Average memory is above 80% for 5 minutes.
5xx More than 50 requests fail with a 5xx status in 5 minutes.
unhealthy A replica fails its health checks. Checked every 5 minutes from the environment logs.

Publishing rules

BRMS publishes releases to the rules container and the Agent loads them from there. In BRMS, add an environment deployment of type Azure Storage. Turn IAM on, set the blob service URL to the storageBlobEndpoint output and the container to rules.

Storage only accepts traffic through its private endpoint. A publisher outside this deployment needs network access to it and Storage Blob Data Contributor on the container.

To password-protect releases, set the same RELEASE_ZIP_PASSWORD in brmsSecrets and agentSecrets. BRMS encrypts the release zips with it and the Agent decrypts them.

Container images

Container Apps pulls public images anonymously from Docker Hub, which rate-limits pulls. For production, copy the images to your own Azure Container Registry and set containerRegistryId and the image fields:

az acr import --name myregistry --source docker.io/gorules/brms:latest --image gorules/brms:latest
az acr import --name myregistry --source docker.io/gorules/agent:latest --image gorules/agent:latest

License

MIT

About

Bicep modules for deploying GoRules on Azure Container Apps

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages