Install on Microsoft Azure
Deploy ScanSuite Teams into your own Azure subscription on Container Apps with Terraform, with installation examples.
ScanSuite Teams can run in your own Azure subscription instead of on a server. A Terraform configuration in the azure folder of the installation repository builds everything with one command and removes it with another. The application runs on Azure Container Apps with managed services around it, so there is no virtual machine to patch.
Container Apps has no Docker daemon, so the scanners that run as containers of their own — dynamic web scanning (DAST) and infrastructure scanning — do not run here. Code (SAST), dependency (SCA), infrastructure-as-code, secrets and AI code analysis all work. For dynamic scans, install on a server: Install ScanSuite Teams.
What gets built
Everything lives in one resource group that the deployment creates. Secrets are generated on the first deploy and kept in Key Vault. The database is reachable only from inside the virtual network, and the artefact storage only from the Container Apps subnet, without storage keys.
| Part | How it runs |
|---|---|
| Web UI and API | A container app with managed TLS; its ingress admits only your address ranges. |
| Background workers | Container apps: housekeeping and recovery of interrupted scans, the scheduler, Redis for the task queue, and the sandbox for AI proof-of-concept checks, started on demand. |
| Scans | One Container Apps job execution per scan, 4 vCPU and 8 GiB, gone when the scan ends. |
| Database migration | A Container Apps job that deploy.sh runs. |
| Database | Azure Database for PostgreSQL 16 (Flexible Server), private, with seven days of backups. |
| Scan artefacts | Blob Storage, reached with a managed identity, earlier versions kept for 30 days. |
| Images | Your own Azure Container Registry: the installation never pulls from the internet at run time. |
| Logs and alerts | Log Analytics, with alerts on errors, failed scans and database load. |
1. Before you start
- An Azure subscription where you are Owner, or Contributor together with Role Based Access Control Administrator: the deployment creates managed identities, a custom role and role assignments, so Contributor alone is not enough.
- The tools, on Linux, macOS, or Windows with WSL or Git Bash: the Azure CLI (
az) 2.60 or newer, Terraform 1.6 or newer, and git and bash. - From your ScanSuite delivery: the licence file (
<name>_<code>.lic; the code is also the image tag) and the registry user name and access token, used to copy the images into your subscription. - Your public IPv4 address or ranges, the ones allowed to open the web UI. Ask for IPv4 explicitly, since many connections report an IPv6 address otherwise:
curl -4 -s https://ifconfig.me.
2. Deploy
Get the installation files, add your licence, and change to the azure folder:
git clone https://github.com/cepxeo/scansuite.git
cp /path/to/<name>_<code>.lic scansuite/key/
cd scansuite/azureSign in to Azure and note the subscription to deploy into:
az login
az account list -o tableCopy the example settings and edit the copy. Section 3 has complete examples.
cp terraform.tfvars.example terraform.tfvars| Setting | What to put |
|---|---|
| subscription_id | The subscription ID from az account list. |
| location | The region, such as germanywestcentral, swedencentral or northeurope. |
| resource_group_name | A new resource group name. The deployment creates it, so it must not exist yet. |
| web_allowed_cidrs | The address ranges allowed to open the UI, such as ["203.0.113.10/32"]. |
| image_tag | Your licence code, the <code> in <name>_<code>.lic. |
| alert_email | Optional. Where alerts go. |
Give the registry credentials to the shell — they are used only to copy the images and never saved — and run the deployment:
export DOCKERHUB_USERNAME='<registry user name>'
export DOCKERHUB_TOKEN='<registry access token>'
./deploy.shA first run takes 30 to 40 minutes, most of it Azure building the Container Apps environment. The script checks your tools, sign-in and licence, and whether the region lets your subscription create PostgreSQL, before creating anything; builds the network, database, Key Vault, storage, registry, the Container Apps environment and Redis; copies the ScanSuite images into your registry; starts the web UI and workers and runs the database migration. When it ends with Done, the installation is running and the url output is its address. terraform output prints the outputs again at any time.
If Docker on the same machine already holds the images for your licence code, for example from a server installation, deploy.sh pushes those local copies instead of copying from the registry; remove them first if they may be out of date.
3. Installation examples
Examples A and B are complete terraform.tfvars files; C to E are lines to add to one of them. The registry credentials always come from the shell, as in section 2.
A. A trial installation
The small dev sizes, the UI open to one address:
subscription_id = "00000000-0000-0000-0000-000000000000"
location = "swedencentral"
resource_group_name = "rg-scansuite-trial"
environment = "dev"
image_tag = "a1b2c3"
web_allowed_cidrs = ["203.0.113.10/32"]
timezone = "Europe/Berlin"
# Cold start instead of an always-warm UI: the first visit after a quiet
# period waits about a minute.
web_min_replicas = 0When the trial is over, ./destroy.sh removes the whole resource group.
B. A production installation
A zone-redundant database with a standby, zone-redundant storage, a Premium registry, Key Vault purge protection and a dedicated compute profile for the scans, plus a fixed outbound address and unlimited logs:
subscription_id = "00000000-0000-0000-0000-000000000000"
location = "germanywestcentral"
resource_group_name = "rg-scansuite"
environment = "prod"
image_tag = "a1b2c3"
web_allowed_cidrs = ["198.51.100.0/24", "203.0.113.10/32"] # office and VPN
timezone = "Europe/Berlin"
alert_email = "secops@acme.example"
postgres_sku = "GP_Standard_D2ds_v5"
enable_nat_gateway = true
log_daily_quota_gb = -1Expect several hundred USD a month more than dev. Before anyone else runs the deployment, move the Terraform state to Azure Storage, as backend.tf.example describes.
C. A fixed outbound address
Outbound traffic otherwise leaves from the environment's address, which can change. Where your git server or AI endpoint allows only listed addresses, add a NAT gateway (about 35 USD a month plus traffic); the egress_ip output is then the address to allow:
enable_nat_gateway = trueD. A second installation in the same subscription
Such as a test installation next to production. Deploy it from its own copy of the folder, so it has its own Terraform state (cp -r scansuite/azure scansuite/azure-test), into its own resource group. Globally unique names get a random suffix, so nothing else has to change:
resource_group_name = "rg-scansuite-test"E. A region where PostgreSQL is not offered to you
Some subscription types, Visual Studio ones among them, cannot create PostgreSQL in every region; deploy.sh stops before building anything and says so. Pick another region:
location = "swedencentral" # or northeurope, francecentral4. First sign-in
Open the url output right away. A new installation shows the setup page, where you create the first account and name your team, as on a server — see Install ScanSuite Teams. Whoever opens it first gets that account, which is why web_allowed_cidrs is set before the deploy.
Then configure the AI provider on the System AI card under System Settings → Shared services (or for one team under Teams → AI → AI provider) — see AI providers and cost. For Azure OpenAI, choose the OpenAI provider, set the endpoint to your resource's v1 address, https://<resource>.openai.azure.com/openai/v1, with its API key, and use a deployment name as the model name.
5. Day-to-day operation
- Change a setting: edit
terraform.tfvarsand run./deploy.shagain. Re-running is always safe, and while the images stay the same, running scans carry on. - A new release: put the new
.licin../key/and remove the old one, setimage_tagto the new code, rungit pull, then./deploy.sh. The migration cancels scans still running, so update when none is. - Interrupted scans recover by themselves: a scan whose container Azure stopped shows no progress for 90 minutes and is then started again, continuing its AI analysis from the last saved stage.
- Logs: open the Log Analytics workspace in the resource group, choose Logs, and query
ContainerAppConsoleLogs_CLbyContainerAppName_s(such asscansuite-web) orContainerJobName_s(scansuite-sastfor scans). - Save money when nobody uses it:
web_min_replicas = 0, and stop the database withaz postgres flexible-server stop; Azure starts it again after seven days. - Keep the Terraform state safe:
terraform.tfstateholds the generated passwords and your licence. Keep it private, back it up after every deploy, and move it to Azure Storage before a second person or a pipeline deploys.
Rough monthly costs in USD for the default dev settings; check your region in the Azure pricing calculator:
| Item | Approximate cost |
|---|---|
| Always-on containers (web, workers, scheduler, Redis) | 120-160 |
| PostgreSQL B_Standard_B1ms with 32 GB | 20 |
| Log Analytics, capped at 1 GB a day | 0-70 |
| Registry, Key Vault, storage | about 10 |
| Scans, 4 vCPU and 8 GiB each | about 0.45 per scan-hour |
| NAT gateway, only if enabled | 35 plus traffic |
6. Tear down
./destroy.shThe script asks you to type the resource group name, then deletes everything the deployment created, the database with all scan data included, in about 15 minutes; -y skips the question. az group show -n <resource group> then reports that the group was not found. If the script stops with polling support for the Content-Type "" was not implemented, Azure has already deleted what the message names: run it again, two or three times if need be.
PostgreSQL Flexible Server 16 is not available to this subscription: example E. ManagedEnvironmentCapacityHeavyUsageError: the region has no room for a new environment right now; delete the failed one (az containerapp env delete) and try again later, or in another region. no licence for image tag: put the .lic in ../key/ and match image_tag to its code. could not import docker.io/appsec4u/...: export the registry credentials in the same shell. Provider produced inconsistent result after apply, after re-creating a deployment with the same resource group name: use a new name, or wait 15 minutes.
Last reviewed 2026-10-01