Deployment

Install on Google Cloud

Deploy ScanSuite Teams into your own Google Cloud project on Cloud Run with Terraform, with installation examples.

ScanSuite Teams can run in your own Google Cloud project instead of on a server. A Terraform configuration in the gcp folder of the installation repository builds everything with one command and removes it with another. The application runs on Cloud Run with managed services around it, so there is no virtual machine to patch.

Scope: static analysis

Cloud Run 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 project. Passwords are generated on the first deploy and kept in Secret Manager; the database and Redis have private addresses only; the images are read through the project's Artifact Registry, pinned to exact digests.

PartHow it runs
Web UI and APIA Cloud Run service behind an external HTTPS load balancer with Cloud Armor, which admits only your address ranges.
Background workersCloud Run worker pools: housekeeping and recovery of interrupted scans, the scheduler, and the sandbox for AI proof-of-concept checks.
ScansOne Cloud Run job execution per scan, 4 vCPU and 16 GiB, gone when the scan ends.
Database migrationA Cloud Run job that deploy.sh runs before the services start.
DatabaseCloud SQL for PostgreSQL 16, private IP, nightly backups and point-in-time recovery.
Task queueMemorystore for Redis, private IP, TLS.
Scan artefactsA Cloud Storage bucket, with earlier versions kept for 30 days.
Outbound trafficCloud NAT on fixed addresses, for allowlists at your git server and AI endpoint.
MonitoringAn uptime check and alerts on errors, failed uploads, database and Redis load.

1. Before you start

  • A Google Cloud project with billing, where your account is Owner, or Editor plus Project IAM Admin: the deployment creates service accounts and grants them roles. A project of its own for ScanSuite is simplest.
  • The tools, on Linux, macOS, or Windows with WSL or Git Bash: the Google Cloud CLI (gcloud), Terraform 1.6 or newer, and git, bash and curl.
  • 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.
  • Your public IPv4 address or ranges, the ones allowed to open the web UI: curl -4 -s https://ifconfig.me.

2. Deploy

Get the installation files, add your licence, and change to the gcp folder:

bash
git clone https://github.com/cepxeo/scansuite.git
cp /path/to/<name>_<code>.lic scansuite/key/
cd scansuite/gcp

Sign in to Google Cloud twice: once for gcloud, and once for Terraform (Application Default Credentials):

bash
gcloud auth login
gcloud auth application-default login

Copy the example settings and edit the copy. Section 3 has complete examples.

Shell
cp terraform.tfvars.example terraform.tfvars
SettingWhat to put
project_idYour project ID (gcloud projects list).
regionSuch as europe-west3 (Frankfurt, the default). With another region also set zones and primary_zone - example D.
image_tagYour licence code, the <code> in <name>_<code>.lic.
dockerhub_usernameThe registry user name sent with your licence.
web_allowed_cidrsThe address ranges allowed to open the UI, up to 10, such as ["203.0.113.10/32"].
timezoneFor scheduled scans, such as Europe/Berlin.
alert_emailOptional. Where alerts go.

Give the registry token to the shell — it is kept in Secret Manager, readable only by Artifact Registry, and never written to the settings file — and run the deployment:

bash
export TF_VAR_dockerhub_token='<registry access token>'
./deploy.sh

A first run takes 20 to 30 minutes, most of it Cloud SQL. The script checks your sign-in, the project's billing and the licence before creating anything; creates the project's Artifact Registry and finds the exact images for your licence code; builds the network, database, Redis, storage and secrets and runs the database migration; then starts the services, the load balancer and monitoring. 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.

3. Installation examples

Examples A, B and C are complete terraform.tfvars files; D to G are lines to add to one of them. The registry token always comes from the shell, as in section 2.

A. A trial installation

Small sizes without standby copies, the UI open to one address — about half the cost of the defaults:

terraform.tfvars
project_id  = "acme-scansuite-trial"
region      = "europe-west3"
environment = "dev"

image_tag          = "a1b2c3"
dockerhub_username = "<registry user name>"

web_allowed_cidrs = ["203.0.113.10/32"]
timezone          = "Europe/Berlin"

# Smaller than the production defaults, without standby copies.
cloudsql_ha      = false
cloudsql_tier    = "db-custom-1-3840"   # 1 vCPU, 3.75 GB
cloudsql_disk_gb = 20
redis_ha         = false
redis_memory_gb  = 1
poc_replicas     = 1

When the trial is over, ./destroy.sh --delete-artifacts removes everything, the stored scan artefacts included.

B. A production installation

The default sizes (a standby database and Redis), your own domain with a Google-managed certificate, alerts, and protection against accidental deletion:

terraform.tfvars
project_id  = "acme-scansuite"
region      = "europe-west3"
environment = "prod"

image_tag          = "a1b2c3"
dockerhub_username = "<registry user name>"

domain_name       = "scansuite.acme.example"
web_allowed_cidrs = ["198.51.100.0/24", "203.0.113.10/32"]   # office and VPN
timezone          = "Europe/Berlin"
alert_email       = "secops@acme.example"

deletion_protection = true

After the first deploy, point the domain's DNS A record at the load_balancer_ip output; the certificate is issued once the name resolves, which can take up to an hour. manage_dns = true creates the DNS zone as well, which you then delegate at your registrar. Before anyone else runs the deployment, move the Terraform state to a Cloud Storage bucket, as backend.tf.example describes.

C. No access from the internet

For a corporate network that reaches the project's VPC over VPN or Interconnect. There is no load balancer; the UI is served on its Cloud Run address to callers inside the network only:

terraform.tfvars
project_id = "acme-scansuite"
region     = "europe-west3"

image_tag          = "a1b2c3"
dockerhub_username = "<registry user name>"

internal_only = true
timezone      = "Europe/Berlin"
alert_email   = "secops@acme.example"

The url output is then the https://…run.app address, which your network resolves and routes through Private Google Access. web_allowed_cidrs and domain_name are not used, and the uptime alert is left out: Google's probers cannot reach an internal address.

D. Another region

Set the region and its zones together; the zones place Redis and the database's standby:

terraform.tfvars
region       = "europe-west4"
zones        = ["europe-west4-a", "europe-west4-b", "europe-west4-c"]
primary_zone = "europe-west4-b"

E. A second installation in the same project

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/gcp scansuite/gcp-test), and give it its own prefix, so every resource gets its own name:

terraform.tfvars
name_prefix = "scansuite-test"

Keep the prefix short: the artefact bucket is named <name_prefix>-artifacts-<project_id>, at most 63 characters.

F. Images from your own registry

Where the installation must not read Docker Hub at all, copy the three images into the project's registry once per release with ./scripts/mirror-images.sh <project> <code> <region> (Docker needed, signed in with the registry user). It prints the lines to add; image_tag and dockerhub_username are then not needed:

terraform.tfvars
image_web        = "europe-west3-docker.pkg.dev/acme-scansuite/scansuite/teams-web:a1b2c3"
image_worker     = "europe-west3-docker.pkg.dev/acme-scansuite/scansuite/teams-worker:a1b2c3"
image_worker_poc = "europe-west3-docker.pkg.dev/acme-scansuite/scansuite/teams-worker-poc:a1b2c3"

G. Deploying again soon after a teardown

Cloud SQL keeps a deleted instance's name for about a week. To deploy again in that time, give the database another name:

terraform.tfvars
cloudsql_name = "scansuite-pg-2"

4. First sign-in

Open the url output right away. Without a domain name the address is the load balancer's IP with a self-signed certificate, so the browser warns once. 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:

  • Vertex AI needs no key: the installation's own service account already has the Vertex AI User role. Enter your project ID, a region where the model is offered and the Claude model ID, and leave the credentials empty. Enable the model for your project in Vertex AI → Model Garden first.
  • Any OpenAI-compatible endpoint works too, Azure OpenAI included. Scans reach it from the fixed addresses in the scan_egress_ips output.

5. Day-to-day operation

  • Change a setting: edit terraform.tfvars and run ./deploy.sh again. Re-running is always safe, and while the images stay the same, running scans carry on.
  • A new release: put the new .lic in ../key/ and remove the old one, set image_tag to the new code, run git pull, then ./deploy.sh. The migration cancels scans still running, so update when none is.
  • Interrupted scans recover by themselves: a scan whose execution stopped shows no progress for 90 minutes and is then started again, continuing its AI analysis from the last saved stage.
  • Browser uploads are limited to 32 MiB on Cloud Run; scan larger code from its git repository.
  • Logs: in the console under Logging → Logs Explorer, or with gcloud logging read for the scansuite-web service and the scansuite-sast job.
  • Keep the Terraform state safe: terraform.tfstate holds the generated passwords and your licence. Keep it private, back it up after every deploy, and move it to a Cloud Storage bucket before a second person or a pipeline deploys.

Rough monthly costs in USD; check your region in the Google Cloud pricing calculator:

ItemDefaultsTrial (example A)
Cloud SQL200-25050-70
Memorystore150-20035-45
Cloud Run services300-400200-250
Load balancer, Cloud Armor, Cloud NAT50-8050-80
Storage, secrets, registry, logs10-3010-30
Scans, 4 vCPU and 16 GiB eachabout 0.5 per scan-hourthe same

6. Tear down

bash
./destroy.sh                      # asks you to type the project ID
./destroy.sh --delete-artifacts   # also delete the stored scan artefacts

It deletes everything the deployment created, the database with all scan data included, in about 15 minutes; -y skips the question. The scan artefacts' bucket is kept unless you pass --delete-artifacts. If a run stops part-way, run it again: it carries on with what is left. One stop is normal — Cloud Run keeps a few addresses in the network for up to two hours after its services are deleted, and only Google can release them. The script says so; nothing left at that point is billed.

Common messages

billing is not enabled: link a billing account to the project. could not find teams-web:<code>: check image_tag and the registry user and token. The Cloud SQL instance already exists: example G. A zone error after changing the region: example D. An API "has not been used in project": a newly enabled API needs a few minutes; run ./deploy.sh again. The page does not load: check that your address is in web_allowed_cidrs; a new load balancer can take 5 to 10 minutes to answer.

Last reviewed 2026-10-01