Installing USCP on Google Cloud
Three paths. Read README.md first for DNS, OIDC, and licensing prerequisites.
- Path A — Terraform (recommended) — one
terraform apply
provisions a static IP, firewall rules, and a VM that runs the installer via cloud-init.
- Path B — Compute Engine VM + install.sh — a single
VM driven by the one-command installer via startup script.
- Path C — GKE + Helm — HA, autoscaling, with Cloud SQL.
Google is the natural OIDC provider here — issuer https://accounts.google.com, redirect URI https://<your-domain>/auth/callback. See post-install.md → Google.
Path A — Terraform (recommended)
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:
terraform output public_ip→ create the DNS A record for your domain pointing at it.- Confirm
https://<domain>/auth/callbackis an authorized redirect URI on your Google OAuth
client.
- Watch bring-up:
gcloud compute ssh uscp-control-plane --zone <zone> --command 'sudo tail -f /var/log/uscp-bootstrap.log'. - 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.
Step 2 — Reserve a static IP (recommended)
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
sudois 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=disableis 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
postgresuser 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.