Skip to main content

Deploying to Google Cloud

A sequence of gcloud commands that runs OrbisID on Cloud Run against a Cloud SQL for PostgreSQL instance, pulling the official orbisid/orbisid-backend and orbisid/orbisid-frontend images directly from Docker Hub — no Artifact Registry, no image build step. GCP doesn't have a declarative template service in common use (Deployment Manager is legacy), so this is a step-by-step walkthrough instead of a single template like the AWS/Azure guides — run each block in order, in the same terminal session, since later steps reuse the variables set in Step 0.

Architecture​

The load balancer's path-based routing mirrors what nginx.conf does for a Docker Compose install (/api/ and /actuator/ to the backend, everything else to the frontend).

Why a VPC connector instead of Cloud Run's built-in --add-cloudsql-instances flag: that flag's Unix-socket connection method needs the GCP-specific postgres-socket-factory JDBC driver, which the backend doesn't bundle — it's built to run identically on any cloud or on-premise, using the plain PostgreSQL JDBC driver over standard TCP. Private IP + a Serverless VPC Access connector gets the same "never touches the public internet" property using that plain driver.

Prerequisites​

  • gcloud CLI installed and authenticated (gcloud auth login), with a project that has billing enabled
  • A domain name you control

A domain is required, unlike Azure. GCP's external HTTPS load balancer needs a real hostname for both the managed TLS certificate and the host-based routing rule — there's no equivalent of Front Door's automatic *.azurefd.net endpoint. You don't need the DNS record in place before starting, but the certificate stays PROVISIONING (and HTTPS won't work) until it is.

Deploy​

Step 0: Set variables​

Run this first — every later step reuses these in the same terminal session:

export PROJECT_ID=my-gcp-project
export REGION=us-central1
export DOMAIN=orbisid.example.com
export ENVIRONMENT_NAME=orbisid
export ORBISID_VERSION=latest # pin to a specific release (e.g. 2.12.2) for production
export DB_TIER=db-custom-1-3840 # 1 vCPU / 3.75 GB

export NETWORK="${ENVIRONMENT_NAME}-vpc"
export CONNECTOR="${ENVIRONMENT_NAME}-connector"
export SQL_INSTANCE="${ENVIRONMENT_NAME}-postgres"
export DB_NAME=orbisid
export DB_USERNAME=orbisid
export BACKEND_SERVICE="${ENVIRONMENT_NAME}-backend"
export FRONTEND_SERVICE="${ENVIRONMENT_NAME}-frontend"
export STATIC_IP_NAME="${ENVIRONMENT_NAME}-ip"
export CERT_NAME="${ENVIRONMENT_NAME}-cert"

export DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9')
export ENCRYPTION_KEY=$(openssl rand -base64 32)
echo "Save this — it cannot be recovered if lost: $ENCRYPTION_KEY"

gcloud config set project "${PROJECT_ID}"

Step 1: Enable required APIs​

gcloud services enable \
run.googleapis.com \
sqladmin.googleapis.com \
compute.googleapis.com \
secretmanager.googleapis.com \
vpcaccess.googleapis.com \
servicenetworking.googleapis.com

Step 2: Networking — VPC, private services access, VPC connector​

gcloud compute networks create "${NETWORK}" --subnet-mode=auto

gcloud compute addresses create "${ENVIRONMENT_NAME}-psa-range" \
--global --purpose=VPC_PEERING --prefix-length=16 --network="${NETWORK}"

gcloud services vpc-peerings connect \
--service=servicenetworking.googleapis.com \
--ranges="${ENVIRONMENT_NAME}-psa-range" \
--network="${NETWORK}"

gcloud compute networks vpc-access connectors create "${CONNECTOR}" \
--region="${REGION}" --network="${NETWORK}" --range=10.8.0.0/28

Step 3: Cloud SQL for PostgreSQL (private IP only)​

This is the slowest step — expect 10–15 minutes for instance creation.

gcloud sql instances create "${SQL_INSTANCE}" \
--database-version=POSTGRES_16 \
--tier="${DB_TIER}" \
--region="${REGION}" \
--network="${NETWORK}" \
--no-assign-ip \
--storage-auto-increase

gcloud sql databases create "${DB_NAME}" --instance="${SQL_INSTANCE}"

gcloud sql users create "${DB_USERNAME}" --instance="${SQL_INSTANCE}" --password="${DB_PASSWORD}"

export DB_PRIVATE_IP=$(gcloud sql instances describe "${SQL_INSTANCE}" --format='value(ipAddresses[0].ipAddress)')
echo "Cloud SQL private IP: ${DB_PRIVATE_IP}"

Step 4: Store secrets in Secret Manager​

printf '%s' "${DB_PASSWORD}" | gcloud secrets create "${ENVIRONMENT_NAME}-db-password" --data-file=- --replication-policy=automatic
printf '%s' "${ENCRYPTION_KEY}" | gcloud secrets create "${ENVIRONMENT_NAME}-encryption-key" --data-file=- --replication-policy=automatic

Step 5: Deploy the backend to Cloud Run​

gcloud run deploy "${BACKEND_SERVICE}" \
--image="docker.io/orbisid/orbisid-backend:${ORBISID_VERSION}" \
--region="${REGION}" \
--port=8080 \
--vpc-connector="${CONNECTOR}" \
--vpc-egress=private-ranges-only \
--ingress=internal-and-cloud-load-balancing \
--allow-unauthenticated \
--min-instances=1 \
--set-env-vars="SPRING_PROFILES_ACTIVE=docker,SPRING_DATASOURCE_URL=jdbc:postgresql://${DB_PRIVATE_IP}:5432/${DB_NAME},SPRING_DATASOURCE_USERNAME=${DB_USERNAME}" \
--set-secrets="SPRING_DATASOURCE_PASSWORD=${ENVIRONMENT_NAME}-db-password:latest,ORBISID_ENCRYPTION_KEY=${ENVIRONMENT_NAME}-encryption-key:latest"

Step 6: Deploy the frontend to Cloud Run​

gcloud run deploy "${FRONTEND_SERVICE}" \
--image="docker.io/orbisid/orbisid-frontend:${ORBISID_VERSION}" \
--region="${REGION}" \
--port=3000 \
--ingress=internal-and-cloud-load-balancing \
--allow-unauthenticated \
--min-instances=1 \
--set-env-vars="NEXT_PUBLIC_API_URL="

Step 7: Reserve a static IP and create Serverless NEGs​

gcloud compute addresses create "${STATIC_IP_NAME}" --global
export LB_IP=$(gcloud compute addresses describe "${STATIC_IP_NAME}" --global --format='value(address)')
echo "Static IP: ${LB_IP}"

gcloud compute network-endpoint-groups create "${BACKEND_SERVICE}-neg" \
--region="${REGION}" --network-endpoint-type=serverless --cloud-run-service="${BACKEND_SERVICE}"

gcloud compute network-endpoint-groups create "${FRONTEND_SERVICE}-neg" \
--region="${REGION}" --network-endpoint-type=serverless --cloud-run-service="${FRONTEND_SERVICE}"

Step 8: Backend services and path-based URL map​

gcloud compute backend-services create "${BACKEND_SERVICE}-bs" --global --load-balancing-scheme=EXTERNAL_MANAGED
gcloud compute backend-services add-backend "${BACKEND_SERVICE}-bs" \
--global --network-endpoint-group="${BACKEND_SERVICE}-neg" --network-endpoint-group-region="${REGION}"

gcloud compute backend-services create "${FRONTEND_SERVICE}-bs" --global --load-balancing-scheme=EXTERNAL_MANAGED
gcloud compute backend-services add-backend "${FRONTEND_SERVICE}-bs" \
--global --network-endpoint-group="${FRONTEND_SERVICE}-neg" --network-endpoint-group-region="${REGION}"

gcloud compute url-maps create "${ENVIRONMENT_NAME}-lb" --default-service="${FRONTEND_SERVICE}-bs"

gcloud compute url-maps add-path-matcher "${ENVIRONMENT_NAME}-lb" \
--path-matcher-name=api-matcher \
--default-service="${FRONTEND_SERVICE}-bs" \
--path-rules="/api/*=${BACKEND_SERVICE}-bs,/actuator/*=${BACKEND_SERVICE}-bs" \
--new-hosts="${DOMAIN}"

Step 9: Managed TLS certificate and HTTPS listener​

gcloud compute ssl-certificates create "${CERT_NAME}" --domains="${DOMAIN}" --global

gcloud compute target-https-proxies create "${ENVIRONMENT_NAME}-https-proxy" \
--ssl-certificates="${CERT_NAME}" --url-map="${ENVIRONMENT_NAME}-lb"

gcloud compute forwarding-rules create "${ENVIRONMENT_NAME}-https-rule" \
--global --address="${STATIC_IP_NAME}" --target-https-proxy="${ENVIRONMENT_NAME}-https-proxy" --ports=443

Step 10: HTTP → HTTPS redirect​

cat <<EOF | gcloud compute url-maps import "${ENVIRONMENT_NAME}-http-redirect" --source=-
name: ${ENVIRONMENT_NAME}-http-redirect
defaultUrlRedirect:
httpsRedirect: true
redirectResponseCode: MOVED_PERMANENTLY_DEFAULT
EOF

gcloud compute target-http-proxies create "${ENVIRONMENT_NAME}-http-proxy" --url-map="${ENVIRONMENT_NAME}-http-redirect"

gcloud compute forwarding-rules create "${ENVIRONMENT_NAME}-http-rule" \
--global --address="${STATIC_IP_NAME}" --target-http-proxy="${ENVIRONMENT_NAME}-http-proxy" --ports=80

Post-deployment​

  1. Point ${DOMAIN}'s DNS A record at the static IP printed in Step 7, if you haven't already.

  2. Wait for the managed certificate to finish provisioning:

    gcloud compute ssl-certificates describe "${CERT_NAME}" --global --format='value(managed.status)'

    This can take anywhere from a few minutes to a few hours after the DNS record resolves.

  3. Browse to https://your-domain. Log in with the default credentials (admin / ChangeMe123!) and change the password immediately — Administration > Users.

  4. Add target systems and configure scanning as usual — see Systems and Scanning.

Cost (rough estimate)​

With the sizing used above (db-custom-1-3840 Cloud SQL, one always-on instance each for backend/frontend via --min-instances=1, one VPC connector): roughly $100–160/month. This is a rough order of magnitude, not a quote — check the GCP Pricing Calculator for your region and traffic. --min-instances=1 keeps both Cloud Run services always warm (no cold-start on login) but means you're paying for idle capacity too; drop it if occasional cold starts are acceptable for your use case.

Updating​

Redeploy each Cloud Run service with a new image tag — this creates a new revision and shifts traffic to it, same as any other Cloud Run deploy:

export ORBISID_VERSION=2.13.0

gcloud run deploy "${BACKEND_SERVICE}" --image="docker.io/orbisid/orbisid-backend:${ORBISID_VERSION}" --region="${REGION}"
gcloud run deploy "${FRONTEND_SERVICE}" --image="docker.io/orbisid/orbisid-frontend:${ORBISID_VERSION}" --region="${REGION}"

Cloud Run keeps every other setting (env vars, secrets, VPC connector, scaling) from the previous revision when you only change --image. The database and its data are untouched. Database migrations run automatically on backend startup (Flyway), same as the Docker Compose install.

Known limitations​

  • No Kafka. Endpoint Sensor / Threat Detection event ingestion (KAFKA_BOOTSTRAP_SERVERS) needs a reachable Kafka broker, which this walkthrough doesn't provision — same as the released Docker Compose packages, which also ship without one. If you need that feature in this deployment, add Google Cloud Managed Service for Apache Kafka (or another Kafka-compatible service reachable via the same VPC connector) and set KAFKA_BOOTSTRAP_SERVERS on the backend Cloud Run service.
  • No bundled AI runtime. Nothing here runs Ollama — AI features stay off. To enable them, either point Administration > Settings > OrbisAI's base URL at a separately-hosted Ollama instance reachable via the VPC connector, or add another Cloud Run service (or a small always-on GCE VM, since CPU inference is memory-hungry and Cloud Run's per-request billing model fits it poorly) running ollama/ollama — see Requirements.
  • Domain required. As noted above, there's no no-domain quick-start path the way Azure's Front Door default endpoint provides — you need a hostname before HTTPS works.

Tearing down​

There's no single teardown command — delete the pieces in roughly reverse order (assumes the Step 0 variables are still set in your shell session):

gcloud compute forwarding-rules delete "${ENVIRONMENT_NAME}-https-rule" "${ENVIRONMENT_NAME}-http-rule" --global --quiet
gcloud compute target-https-proxies delete "${ENVIRONMENT_NAME}-https-proxy" --quiet
gcloud compute target-http-proxies delete "${ENVIRONMENT_NAME}-http-proxy" --quiet
gcloud compute url-maps delete "${ENVIRONMENT_NAME}-lb" "${ENVIRONMENT_NAME}-http-redirect" --quiet
gcloud compute ssl-certificates delete "${CERT_NAME}" --global --quiet
gcloud compute backend-services delete "${BACKEND_SERVICE}-bs" "${FRONTEND_SERVICE}-bs" --global --quiet
gcloud compute network-endpoint-groups delete "${BACKEND_SERVICE}-neg" "${FRONTEND_SERVICE}-neg" --region="${REGION}" --quiet
gcloud compute addresses delete "${STATIC_IP_NAME}" --global --quiet
gcloud run services delete "${BACKEND_SERVICE}" "${FRONTEND_SERVICE}" --region="${REGION}" --quiet
gcloud sql instances delete "${SQL_INSTANCE}" --quiet
gcloud compute networks vpc-access connectors delete "${CONNECTOR}" --region="${REGION}" --quiet
gcloud secrets delete "${ENVIRONMENT_NAME}-db-password" "${ENVIRONMENT_NAME}-encryption-key" --quiet
Irreversible

gcloud sql instances delete removes the database and all its data with no recovery window. Take a manual export first if you need one:

gcloud sql export sql "${SQL_INSTANCE}" gs://your-backup-bucket/orbisid-backup.sql --database="${DB_NAME}"

The VPC network and its private services access peering are left in place, since other projects/resources may depend on them — delete ${NETWORK} manually if you're sure nothing else uses it.

Troubleshooting​

  • Cloud Run service failing to start: gcloud run services logs read "${BACKEND_SERVICE}" --region="${REGION}" — usually a database connectivity issue (check the VPC connector is READY: gcloud compute networks vpc-access connectors describe "${CONNECTOR}" --region="${REGION}") or a bad DB_PASSWORD/ENCRYPTION_KEY value.
  • Certificate stuck PROVISIONING: almost always DNS — confirm dig your-domain resolves to the static IP from Step 7. Propagation plus Google's own validation can take hours in the worst case.
  • 502 from the load balancer: the backend takes time to run Flyway migrations on first boot; give it a minute after first deploy. If it persists, check the backend service's health via gcloud compute backend-services get-health "${BACKEND_SERVICE}-bs" --global.