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:
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:
Option A: Azure Container Apps (Recommended)¶
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:
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¶
- Sign in: Use the initial admin account. The deploy never prints its password; it is in your package's
.env - Username:
admin(orINITIAL_ADMIN_USERNAME) - Password:
INITIAL_ADMIN_PASSWORD(your package README shows it too) - Change Password: CertifyClouds asks you to set a new password at your first sign-in
- Settings > Subscriptions: Add your Azure subscription IDs
- Test Connection: In Settings → Access, verify CertifyClouds can access Azure
- Scan: Run your first Key Vault scan
- Compliance: Review security posture
- 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¶
- Restore PostgreSQL to a new server or point-in-time
- Update connection string in Container App/ACI environment variables
- 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
Container Apps HA (Recommended)¶
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:
- Configure a generic webhook in CertifyClouds
- Point to your SIEM's HTTP event collector
- Map fields as needed in your SIEM
Support¶
- Email: [email protected]