Skip to content

Installation Guide

Prerequisites

Before you begin, ensure you have:

  • [ ] Azure subscription with Owner or Contributor role
  • [ ] The right to assign roles on your Azure Container Registry (Owner, User Access Administrator or Role Based Access Control Administrator on the registry), because the deploy gives CertifyClouds' managed identity AcrPull on it
  • [ ] Someone who is Global Administrator or Privileged Role Administrator in Microsoft Entra ID, for Step 4
  • [ ] Azure CLI installed and signed in (az login)
  • [ ] A CertifyClouds licence key (provided by CertifyClouds)
  • [ ] Azure Container Registry (ACR) available for the CertifyClouds image
  • [ ] CertifyClouds deployment package, including deployment scripts and registry import credentials
  • [ ] Two delegated subnets in your VNet, or permission to create them during deployment

Running from Windows

The package scripts are plain Bash scripts that use Azure CLI. Run them in a Bash environment; PowerShell is not supported. The scripts have been tested with macOS Bash 3.2 and Linux Bash 5.

Recommended: Azure Cloud Shell (Bash): Open Cloud Shell in the Azure portal and select Bash. az, jq, openssl, and python3 are preinstalled. Use the Cloud Shell Upload file button to upload the customer package ZIP, then extract it and enter the package directory:

unzip <customer-package>.zip
cd <customer-package-directory>

Run the commands in this guide from that directory. This requires no local software installation and no administrator rights on your Windows computer.

Alternative: WSL2 with Ubuntu: Install Azure CLI in Ubuntu, then install jq with sudo apt install jq. Extract the package and run the same Bash commands from its directory.

Git Bash is unsupported. It does not include jq or python3 by default, so the scripts will fail partway through when they reach those tools. Use Azure Cloud Shell (Bash) or WSL2 Ubuntu instead.


Step 1: Import the CertifyClouds Image to Your ACR

Contact CertifyClouds for registry credentials, then import the image to your ACR:

az acr import --name YOUR_ACR \
  --source <PROVIDED_SOURCE_IMAGE> \
  --image certifyclouds:latest \
  --username <PROVIDED_USERNAME> \
  --password <PROVIDED_TOKEN>

Step 2: Create Required Subnets

CertifyClouds requires two subnets in your VNet:

Application Subnet (Container Apps or ACI)

# For Container Apps (minimum /27)
az network vnet subnet create \
  --resource-group <NETWORKING_RG> \
  --vnet-name <VNET_NAME> \
  --name snet-certifyclouds-cae \
  --address-prefixes 10.0.1.0/27 \
  --delegations Microsoft.App/environments \
  --service-endpoints Microsoft.KeyVault Microsoft.ContainerRegistry

# For Container Instances (minimum /28)
az network vnet subnet create \
  --resource-group <NETWORKING_RG> \
  --vnet-name <VNET_NAME> \
  --name snet-certifyclouds-aci \
  --address-prefixes 10.0.1.0/28 \
  --delegations Microsoft.ContainerInstance/containerGroups \
  --service-endpoints Microsoft.KeyVault Microsoft.ContainerRegistry

PostgreSQL Subnet (minimum /28)

az network vnet subnet create \
  --resource-group <NETWORKING_RG> \
  --vnet-name <VNET_NAME> \
  --name snet-certifyclouds-psql \
  --address-prefixes 10.0.1.32/28 \
  --delegations Microsoft.DBforPostgreSQL/flexibleServers

Step 3: Deploy CertifyClouds

On a new install you run the deploy command twice, with Step 4 in between. The first run takes a few minutes: it creates the resource group and the managed identity CertifyClouds runs as, then stops before CertifyClouds starts and prints the access command for Step 4. The second run builds everything else and starts CertifyClouds.

Choose your deployment method:

Fully managed with auto-scaling, health probes, and load balancing. Estimate cost using your Azure region, replica count, CPU/memory allocation, and database size.

./deploy-certifyclouds-cae.sh \
  --name prd \
  --location uksouth \
  --acr-name YOUR_ACR \
  --container-apps-subnet /subscriptions/YOUR_SUB/resourceGroups/YOUR_RG/providers/Microsoft.Network/virtualNetworks/YOUR_VNET/subnets/snet-certifyclouds-cae \
  --postgres-subnet /subscriptions/YOUR_SUB/resourceGroups/YOUR_RG/providers/Microsoft.Network/virtualNetworks/YOUR_VNET/subnets/snet-certifyclouds-psql

Option B: Azure Container Instances

Simpler deployment model with direct private IP access. Estimate cost using your Azure region and CPU/memory allocation.

./deploy-certifyclouds-aci.sh \
  --name prd \
  --location uksouth \
  --acr-name YOUR_ACR \
  --aci-subnet /subscriptions/YOUR_SUB/resourceGroups/YOUR_RG/providers/Microsoft.Network/virtualNetworks/YOUR_VNET/subnets/snet-certifyclouds-aci \
  --postgres-subnet /subscriptions/YOUR_SUB/resourceGroups/YOUR_RG/providers/Microsoft.Network/virtualNetworks/YOUR_VNET/subnets/snet-certifyclouds-psql

What the first run prints

On a new install the first run stops on purpose, before CertifyClouds starts, and exits with code 10. It prints:

⚠ Waiting for access: CertifyClouds has not been started.
  Its managed identity (principal ID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) does not have
  the Microsoft Graph permission Application.Read.All yet, which CertifyClouds needs from its
  first start. Azure keeps the identity's first Microsoft Graph token for up to 24 hours,
  so access granted after CertifyClouds starts would not reach App Registration discovery
  for up to a day.

  1. Grant its access. Assigning the Microsoft Graph permission needs a Global Administrator
     or Privileged Role Administrator. Run the command for your licence from this folder:
     STARTER:
       ./setup-certifyclouds-access.sh --principal-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --subnet-id /subscriptions/.../subnets/snet-certifyclouds-cae --apply
     PRO or ENTERPRISE:
       ./setup-certifyclouds-access.sh --principal-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --subnet-id /subscriptions/.../subnets/snet-certifyclouds-cae --with-rotation --with-resource-updates --apply
  2. Run the same deploy command again afterwards. It reuses everything created so far and
     starts CertifyClouds.

Go to Step 4. In a pipeline, exit code 10 means "grant the access, then run the same command again"; 0 means deployed, and anything else is a failure.

If the managed identity already has this access, for example one your team prepared (see below), the deploy does not stop and finishes in one run.

If the account running the deploy cannot read your Microsoft Entra directory, the deploy cannot tell whether the access is granted. It says so and stops the same way. Once you know the access is granted, add --access-granted to the deploy command to start CertifyClouds without that check.

Optional: Use your own managed identity

CertifyClouds runs as a user-assigned managed identity. By default the deploy creates it as id-cc-<region>-<name> in the deployment's resource group (for example id-cc-uks-prd) and reuses it on every later run. If your organisation provisions identities centrally, add --user-assigned-identity <resource-id> to either deploy command to run as a pre-created one instead:

./deploy-certifyclouds-cae.sh \
  --name prd --location uksouth --acr-name YOUR_ACR \
  --container-apps-subnet <CAE_SUBNET> --postgres-subnet <PSQL_SUBNET> \
  --user-assigned-identity /subscriptions/YOUR_SUB/resourceGroups/YOUR_RG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/YOUR_IDENTITY

A user-assigned identity has two different IDs: pass the resource ID to --user-assigned-identity, and use the principalId for setup-certifyclouds-access.sh. The deploy sets AZURE_CLIENT_ID for you.

Whichever identity CertifyClouds runs as pulls the image, so it needs AcrPull on your Azure Container Registry, and the ACR admin user can stay disabled. The deploy grants AcrPull itself. If that grant is refused and the identity does not already have AcrPull, the deploy stops and prints the command for someone with Owner, User Access Administrator or Role Based Access Control Administrator on the registry to run.


Step 4: Grant Access, Then Deploy Again

From the package folder, run the access command the deploy printed for your licence. Run it as someone who is Global Administrator or Privileged Role Administrator in Microsoft Entra ID (to assign the Microsoft Graph permission) and Owner or User Access Administrator on the subscriptions (to assign the Azure roles):

# STARTER
./setup-certifyclouds-access.sh --principal-id $PRINCIPAL_ID --subnet-id $APP_SUBNET_ID --apply

# PRO or ENTERPRISE (adds the Rotation and full-stack rotation permissions)
./setup-certifyclouds-access.sh --principal-id $PRINCIPAL_ID --subnet-id $APP_SUBNET_ID \
  --with-rotation --with-resource-updates --apply

Without --apply the script only reports what is missing and changes nothing.

The script will:

  • Discover all Key Vaults across your subscriptions
  • Auto-detect RBAC vs Access Policy configuration
  • Grant appropriate read (or write) permissions
  • Grant the Microsoft Graph permission to read App Registrations
  • Add subnet to Key Vault firewall rules if needed

Then run exactly the same deploy command as in Step 3. It reuses the resource group and the managed identity, builds the database and everything else, and starts CertifyClouds. It ends with a summary that includes the address to reach it (the internal URL on Container Apps, the private IP on Container Instances), and writes the same details to deploy-output.json for your own scripts. If the access is not granted yet, it stops again with the same message.

To look up the managed identity's principal ID later:

az identity show --name id-cc-uks-prd --resource-group rg-cc-uks-prd --query principalId -o tsv

Step 5: Access the Application

CertifyClouds is deployed with internal-only ingress - it has no public IP address.

Access Methods

Method Description
VPN Connect to your VNet via VPN Gateway
Azure Bastion Use a jumpbox VM in the same VNet
ExpressRoute Access from on-premises network
Peered VNet Access from a VM in a peered VNet

URLs

  • Container Apps: https://<INTERNAL_FQDN> (shown in deployment output)
  • ACI: http://<PRIVATE_IP>:8080 (shown in deployment output)

Step 6: First-Time Setup

  1. Sign in: Use the initial admin account. The deploy never prints its password; it is in your package's .env
  2. Username: admin (or INITIAL_ADMIN_USERNAME)
  3. Password: INITIAL_ADMIN_PASSWORD (your package README shows it too)
  4. Change Password: CertifyClouds asks you to set a new password at your first sign-in
  5. Settings > Subscriptions: Add your Azure subscription IDs
  6. Test Connection: In Settings → Access, verify CertifyClouds can access Azure
  7. Scan: Run your first Key Vault scan
  8. Compliance: Review security posture
  9. Alerts: Configure notification rules

Configuration Reference

Environment Variables

The deployment scripts configure these automatically. See Environment Setup for the complete reference.

Variable Description
CERTIFYCLOUDS_LICENSE_KEY Your licence key (CC-XXXXXXXX-XXXXXXXX-XXXXXXXX)
INITIAL_ADMIN_USERNAME Initial admin username (default: admin)
INITIAL_ADMIN_PASSWORD Initial admin password (auto-generated if not set)
SECRET_KEY Encryption key for Sync credentials (auto-generated if not set)

Email Configuration (for Alerts)

After deployment, sign in as an administrator and open Settings → Advanced → Email. Configure either SMTP or SendGrid there, save the provider, and send a test email. Provider credentials are stored encrypted. Email is set up only there; email environment variables are not read.


Resource Requirements

Environment Key Vaults Secrets CPU Memory
Small 1-10 <500 1 vCPU 2 GB
Medium 10-50 <2,000 2 vCPU 4 GB
Large 50-200 <10,000 4 vCPU 8 GB

Updating CertifyClouds

When a new version is released, set VERSION to the target release and run:

VERSION=1.8.0   # set to the target release tag (Docker image tag, no "v" prefix)

# 1. Import new version to ACR
az acr import --name YOUR_ACR \
  --source <PROVIDED_SOURCE_IMAGE>:$VERSION \
  --image certifyclouds:$VERSION \
  --username <PROVIDED_USERNAME> \
  --password <PROVIDED_TOKEN>

# 2a. For Container Apps - update image
az containerapp update \
  --name cc-uks-prd \
  --resource-group rg-cc-uks-prd \
  --image YOUR_ACR.azurecr.io/certifyclouds:$VERSION

# 2b. For ACI - redeploy with new version
./deploy-certifyclouds-aci.sh \
  --name prd \
  --location uksouth \
  --acr-name YOUR_ACR \
  --aci-subnet /subscriptions/.../subnets/snet-certifyclouds-aci \
  --postgres-subnet /subscriptions/.../subnets/snet-certifyclouds-psql \
  --image-version $VERSION

The latest available version is surfaced in the dashboard's update banner (populated from the licence server's heartbeat). Your deployment's current running version is shown at the bottom of the Settings page and at GET /api/system/version.


Architecture

CertifyClouds runs as a containerised application within your Azure VNet alongside a managed database. All components are private with no public IP exposure. The application uses Managed Identity to access Key Vaults across your subscriptions.

+------------------------------------------------------------------------+
|  Your Azure Subscription                                                |
|                                                                        |
|  +------------------------------------------------------------------+  |
|  |  Your Existing VNet                                              |  |
|  |                                                                  |  |
|  |  +------------------------+  +-------------------------------+   |  |
|  |  | Application Subnet     |  | Database Subnet               |   |  |
|  |  |                        |  |                               |   |  |
|  |  | +--------------------+ |  | +---------------------------+ |   |  |
|  |  | | CertifyClouds      |----| | Managed Database          | |   |  |
|  |  | | (Container)        | |  | | (VNet integrated)         | |   |  |
|  |  | | - Managed Identity | |  | +---------------------------+ |   |  |
|  |  | +--------------------+ |  +-------------------------------+   |  |
|  |  +------------------------+                                       |  |
|  +------------------------------------------------------------------+  |
|                                                                        |
|  Key Vaults (various subscriptions)                                    |
|  - Accessed via Managed Identity                                       |
+------------------------------------------------------------------------+

Backup & Recovery

What to Back Up

CertifyClouds stores all data in your PostgreSQL database. Back up the database regularly.

Data Location Backup Method
Scan results PostgreSQL Azure Backup
Rotation history PostgreSQL Azure Backup
Alert rules PostgreSQL Azure Backup
Audit logs PostgreSQL Azure Backup
Configuration Environment variables Document/export

Azure PostgreSQL Backup

Azure PostgreSQL Flexible Server includes automatic backups:

# Check backup configuration
az postgres flexible-server show \
  --resource-group rg-cc-uks-prd \
  --name psql-cc-uks-prd \
  --query "backup"

# Create on-demand backup
az postgres flexible-server backup create \
  --resource-group rg-cc-uks-prd \
  --server-name psql-cc-uks-prd \
  --name manual-backup-$(date +%Y%m%d)

Recommended Settings:

  • Retention: 7-35 days (based on compliance requirements)
  • Geo-redundant backup: Enable for DR scenarios
  • Point-in-time restore: Available for last 7 days by default

Recovery Procedure

  1. Restore PostgreSQL to a new server or point-in-time
  2. Update connection string in Container App/ACI environment variables
  3. Restart the application to connect to restored database
# Point-in-time restore
az postgres flexible-server restore \
  --resource-group rg-cc-uks-prd \
  --name psql-cc-uks-prd-restored \
  --source-server psql-cc-uks-prd \
  --restore-time "2026-02-20T10:00:00Z"

High Availability

Application Statelessness

CertifyClouds is stateless - all persistent data is stored in PostgreSQL. This means:

  • Safe to run multiple replicas
  • No session affinity required
  • Can be restarted without data loss

Azure Container Apps provides built-in HA:

# Scale to multiple replicas
az containerapp update \
  --name cc-uks-prd \
  --resource-group rg-cc-uks-prd \
  --min-replicas 2 \
  --max-replicas 4

Benefits:

  • Automatic load balancing
  • Health probe-based failover
  • Rolling updates with zero downtime

Database HA

Azure PostgreSQL Flexible Server HA options:

Option Description Cost
Zone-redundant Standby in different AZ ~2x
Same-zone Standby in same AZ ~1.5x
None Single instance Base
# Enable zone-redundant HA
az postgres flexible-server update \
  --resource-group rg-cc-uks-prd \
  --name psql-cc-uks-prd \
  --zonal-resiliency Enabled

SIEM Integration

Log Sources

CertifyClouds generates logs that can be sent to your SIEM:

Log Type Content Format
Application logs All operations, errors JSON (structured)
Audit logs User actions, changes Stored in PostgreSQL
Azure diagnostics Container metrics, events Azure Monitor format

Azure Monitor Integration

Enable diagnostic settings to send logs to Log Analytics:

# Get Container App resource ID
RESOURCE_ID=$(az containerapp show \
  --name cc-uks-prd \
  --resource-group rg-cc-uks-prd \
  --query id -o tsv)

# Create diagnostic setting
az monitor diagnostic-settings create \
  --name certifyclouds-logs \
  --resource $RESOURCE_ID \
  --workspace /subscriptions/.../resourceGroups/.../providers/Microsoft.OperationalInsights/workspaces/YOUR_WORKSPACE \
  --logs '[{"category": "ContainerAppConsoleLogs", "enabled": true}]'

Webhook to SIEM

Use CertifyClouds webhooks to send alerts to your SIEM:

  1. Configure a generic webhook in CertifyClouds
  2. Point to your SIEM's HTTP event collector
  3. Map fields as needed in your SIEM

Support