Status: 104 capabilities — 20 GA, 84 Beta. Enforcement is monitor-only by default. Honest status →

Installing USCP on Google Cloud

Three paths. Read README.md first for DNS, OIDC, and licensing prerequisites.

provisions a static IP, firewall rules, and a VM that runs the installer via cloud-init.

VM driven by the one-command installer via startup script.

Google is the natural OIDC provider here — issuer https://accounts.google.com, redirect URI https://<your-domain>/auth/callback. See post-install.md → Google.


The module in deploy/terraform/gcp/ provisions a static regional IP, firewall rules (SSH locked to your CIDR; 80/443 public, scoped by the uscp network tag), and an Ubuntu 24.04 VM whose cloud-init runs deploy/install.sh unattended.

Prerequisites

  • Terraform ≥ 1.5 and gcloud auth application-default login.
  • A project with the Compute Engine API enabled.

Steps

git clone https://github.com/cfssay/doublelogic_apex uscp
cd uscp/codes/deploy/terraform/gcp
terraform init
terraform apply \
  -var="project=my-gcp-project" \
  -var="domain=uscp.example.com" \
  -var="oidc_client_id=...apps.googleusercontent.com" \
  -var="oidc_client_secret=GOCSPX-..." \
  -var="acme_email=ops@example.com" \
  -var="ssh_ingress_cidr=203.0.113.4/32" \
  -var="ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)"     # optional; else use gcloud/OS Login

Then:

  1. terraform output public_ip → create the DNS A record for your domain pointing at it.
  2. Confirm https://<domain>/auth/callback is an authorized redirect URI on your Google OAuth

client.

  1. Watch bring-up: gcloud compute ssh uscp-control-plane --zone <zone> --command 'sudo tail -f /var/log/uscp-bootstrap.log'.
  2. Verify https://<domain>/readyz, then first login.

Optional vars: region, zone, machine_type, network, image. For production Postgres, use Cloud SQL and set USCP_DB_MODE=external (see Path B). Full module notes: deploy/terraform/gcp/README.md.


Path B — Compute Engine VM + install.sh

Step 1 — Firewall rules

Compute Engine tags let you scope rules. Create a tag uscp and open the right ports:

gcloud compute firewall-rules create uscp-ssh \
  --allow tcp:22 --target-tags uscp --source-ranges "$(curl -s https://ifconfig.me)/32"
gcloud compute firewall-rules create uscp-web \
  --allow tcp:80,tcp:443 --target-tags uscp --source-ranges 0.0.0.0/0

Port 80 open to the internet is required for the Let's Encrypt HTTP-01 challenge. Do not open 8080 or 5432.

So DNS survives reboots:

gcloud compute addresses create uscp-ip --region us-central1
gcloud compute addresses describe uscp-ip --region us-central1 --format='value(address)'

Step 3 — Create the VM with a startup script

Save this as startup.sh (it mirrors deploy/cloud-init.yaml, using GCP's startup-script mechanism):

#!/usr/bin/env bash
set -euo pipefail
apt-get update && apt-get install -y git docker.io
systemctl enable --now docker
git clone https://github.com/cfssay/doublelogic_apex /opt/uscp
cd /opt/uscp/codes
USCP_DOMAIN=uscp.example.com \
USCP_OIDC_ISSUER=https://accounts.google.com \
USCP_OIDC_CLIENT_ID=1234567890-abcdef.apps.googleusercontent.com \
USCP_OIDC_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxx \
USCP_ACME_EMAIL=ops@example.com \
./deploy/install.sh

Create the instance, attaching the static IP and the startup script:

gcloud compute instances create uscp \
  --zone us-central1-a \
  --machine-type e2-medium \                        # 2 vCPU / 4 GB — the minimum
  --image-family ubuntu-2204-lts --image-project ubuntu-os-cloud \
  --boot-disk-size 20GB \
  --tags uscp \
  --address uscp-ip \
  --metadata-from-file startup-script=startup.sh

The startup script runs as root on boot, so no sudo is needed inside it. Because the OIDC secret is in the startup script, delete the metadata after first boot (gcloud compute instances remove-metadata uscp --zone us-central1-a --keys startup-script) or use Secret Manager and fetch at boot.

Step 4 — Point DNS at the static IP

Create A uscp.example.com → <static IP> and confirm:

dig +short uscp.example.com

Step 5 — Watch the install and verify

gcloud compute ssh uscp --zone us-central1-a
sudo journalctl -u google-startup-scripts -f       # watch the startup script run install.sh

curl -fsS https://uscp.example.com/healthz
curl -fsS https://uscp.example.com/readyz

If ACME failed because DNS wasn't ready, sudo systemctl restart caddy after the record resolves. Then continue with first login.

Manual variant (SSH and run by hand)

gcloud compute instances create uscp --zone us-central1-a --machine-type e2-medium \
  --image-family ubuntu-2204-lts --image-project ubuntu-os-cloud --boot-disk-size 20GB \
  --tags uscp --address uscp-ip
gcloud compute ssh uscp --zone us-central1-a
sudo apt-get update && sudo apt-get install -y git docker.io && sudo systemctl enable --now docker
git clone https://github.com/cfssay/doublelogic_apex uscp && cd uscp/codes
sudo \
  USCP_DOMAIN=uscp.example.com \
  USCP_OIDC_ISSUER=https://accounts.google.com \
  USCP_OIDC_CLIENT_ID=... USCP_OIDC_CLIENT_SECRET=... \
  USCP_ACME_EMAIL=ops@example.com \
  ./deploy/install.sh

Using Cloud SQL for PostgreSQL (production)

Provision Cloud SQL for PostgreSQL 16, create a database and a non-superuser role uscp_app, and connect. The simplest secure path is the Cloud SQL Auth Proxy on the VM, which exposes the database on 127.0.0.1:5432:

# On the VM, run the auth proxy (as a service), then:
sudo \
  USCP_DOMAIN=uscp.example.com \
  USCP_OIDC_ISSUER=https://accounts.google.com \
  USCP_OIDC_CLIENT_ID=... USCP_OIDC_CLIENT_SECRET=... \
  USCP_ACME_EMAIL=ops@example.com \
  USCP_DB_MODE=external \
  DATABASE_URL='postgres://uscp_app:PASSWORD@127.0.0.1:5432/uscp?sslmode=disable' \
  ./deploy/install.sh
  • sslmode=disable is safe only because the Auth Proxy provides the encrypted tunnel to a

loopback socket. For a direct private-IP connection, use sslmode=require and the Cloud SQL CA.

  • Use a non-superuser role — Cloud SQL's default postgres user can bypass row-level

security, breaking tenant isolation. See post-install.md → Database hardening.


Path C — GKE + Helm

For HA. Chart in deploy/helm/uscp/; full reference in kubernetes.md. GCP specifics:

Step 1 — Cluster, database, registry

gcloud container clusters create-auto uscp-gke --region us-central1
gcloud container clusters get-credentials uscp-gke --region us-central1

Provision Cloud SQL for PostgreSQL 16 with a non-superuser uscp_app role. Use the Cloud SQL Auth Proxy sidecar pattern or Private Service Connect for pod → database connectivity.

Step 2 — Build and push the image to Artifact Registry

cd codes
gcloud artifacts repositories create uscp --repository-format=docker --location=us-central1
REG=us-central1-docker.pkg.dev/$(gcloud config get-value project)/uscp
gcloud auth configure-docker us-central1-docker.pkg.dev
docker build -t $REG/controlplane:v1 .              # uses ./Dockerfile
docker push $REG/controlplane:v1

Step 3 — Secret + install

kubectl create namespace uscp
kubectl -n uscp create secret generic uscp-secrets \
  --from-literal=DATABASE_URL='postgres://uscp_app:PASSWORD@127.0.0.1:5432/uscp?sslmode=disable' \
  --from-literal=USCP_OIDC_ISSUER='https://accounts.google.com' \
  --from-literal=USCP_OIDC_CLIENT_ID='...' \
  --from-literal=USCP_OIDC_CLIENT_SECRET='...' \
  --from-literal=USCP_AIRGAP_BUNDLE=/licenses/dev-entitlement.lic \
  --from-literal=USCP_LICENSE_JWKS=/licenses/dev-jwks.json

helm install uscp deploy/helm/uscp -n uscp \
  --set image.repository=$REG/controlplane \
  --set image.tag=v1 \
  --set env.USCP_REGION=us-central1 \
  --set env.USCP_PUBLIC_URL=https://uscp.example.com

(If using the Cloud SQL Auth Proxy sidecar, 127.0.0.1:5432 resolves inside each pod.)

Step 4 — Ingress, TLS, DNS

Expose the Service (port 8080) via a GKE Ingress with a Google-managed certificate, or NGINX + cert-manager. Reserve a global static IP, point uscp.example.com at it, then:

curl -fsS https://uscp.example.com/readyz

Continue at kubernetes.md and post-install.md.