Deploying Portward on Google Cloud
For the person who runs Google Cloud at your company. About 25 minutes. At the end, your team asks Claude or ChatGPT about company data through a gateway that runs in your own Google Cloud project. The gateway swaps the values you mark as sensitive for tokens before the AI sees them, and records every answer in your project. Portward (the company) never receives your data and has no access to your project.
What gets created, all in your project:
| One machine (e2-small, Container-Optimized OS, Shielded VM) | Runs the gateway, Caddy (its HTTPS certificate) and, with the relay, the relay client. |
| A records disk (10 GB, encrypted) | The gateway’s answer records, setup and saved connections. Snapshotted daily, kept 14 days; snapshots stay if the disk is deleted. |
| Secret Manager | The gateway key, generated on the machine the first time it starts (it never leaves your project), and the secrets you add. |
| A new network | Nothing open inbound. With your own domain, ports 80 and 443 only. |
| A service account | May read its own secrets, add the key once and write logs. Nothing else. |
Cost, roughly $18 a month in us-central1: about $12 for the machine, $2 for the disks, $4 for its IP address, and a few cents for snapshots and secrets.
What you need
Section titled “What you need”- A Google Cloud project, and in it roles to create the above (Owner, or Compute Admin, Service Account Admin, Service Account User, Project IAM Admin, Secret Manager Admin, Service Usage Admin).
- Terraform 1.5 or later. With the Deploy to Google Cloud button you don’t need it, because Cloud Shell has it.
- Your company directory, where your team already signs in: Microsoft Entra, Okta, Google Workspace or any OpenID Connect directory.
- Your Portward license key (
pwlic_…) from the key page (Get started, step 2). The gateway uses it to download its image and to connect to the relay. Your gateway’s name is part of it. - An address. Either the Portward relay (
https://yourco.gw.portward.ai, from the license key; nothing opens inbound), or a name of your own such asportward.yourcompany.comthat you can point at an IP. - Read-only logins for the databases you’ll connect. Portward refuses a login that can write.
1. Register the gateway in your directory (10 minutes)
Section titled “1. Register the gateway in your directory (10 minutes)”Register one web app with the redirect address https://<your address>/auth/callback. Sign-in setup
has the steps for Entra, Okta and Google. Keep its sign-in address (issuer), client id and secret.
2. Deploy (5 minutes, then about 3 for the machine)
Section titled “2. Deploy (5 minutes, then about 3 for the machine)”The quick way: press Deploy to Google Cloud (also on the page that showed your license key). Cloud Shell opens with this module and a step-by-step tutorial beside it. Google runs repositories it doesn’t maintain in a temporary session. The tutorial has you sign in there and keeps Terraform’s state in a private bucket in your project. It asks for the license key and directory secret without showing them, and never takes them from the link. It runs the same module as below.
Or from your own Terraform:
module "portward" { source = "https://portward-launch.s3.us-east-2.amazonaws.com/templates/1.1.1/gcp/portward-gcp.zip" project_id = "your-project" zone = "us-central1-a"
# Your license key from signing up (pwlic_yourco_...): the image, the relay key and the address, # https://yourco.gw.portward.ai through the Portward relay ... license_key = var.portward_license # e.g. TF_VAR_portward_license from your vault # ... or your own: address_mode = "own-domain" domain = "portward.yourcompany.com"
directory_issuer = "https://login.microsoftonline.com/<tenant id>/v2.0" directory_client_id = "<client id>" admin_groups = ["<group object id>"] # Google Workspace: admin_subjects = ["you@yourco.com"] user_groups = ["<group object id>"] # Google Workspace: users = ["@yourco.com"]}terraform init && terraform applyTo reach databases in an existing network, add subnetwork = "projects/<p>/regions/<r>/subnetworks/<name>"; with
no public IP there (Cloud NAT instead), also public_ip = false.
If Compute Engine wasn’t turned on in the project before, Google creates a default network with SSH and RDP open
to the internet when it’s turned on. The gateway doesn’t use it (it runs in its own network); delete it if nothing
else does.
3. Add the directory secret (2 minutes)
Section titled “3. Add the directory secret (2 minutes)”The deployment puts the license key in Secret Manager. It is also in Terraform’s state, so keep the state private. The directory secret can stay out of the state: terraform output secrets_to_add lists the command. From a
file, so it isn’t typed on a command line:
gcloud secrets versions add portward-directory-secret --project your-project --data-file=directory-secret.txtThe machine checks every 30 seconds and starts the gateway once it is there. (You can instead pass it as
directory_client_secret.)
With your own domain: point it at terraform output ip_address (a DNS A record). Caddy gets the certificate as soon
as the name resolves.
4. Check it
Section titled “4. Check it”About a minute after the last secret is in, the gateway is up; through the relay, allow another minute or two for the certificate.
curl -s https://<your address>/health # {"ok": true, ...}The machine’s progress is in its serial console (gcloud compute instances get-serial-port-output portward-gateway,
lines starting portward:), and the gateway’s own log in Cloud Logging (terraform output logs).
5. Make yourself the admin, connect data, give your team access
Section titled “5. Make yourself the admin, connect data, give your team access”Your settings are the module’s values. If you deployed with your own Terraform, edit the module "portward" block.
If you used the Deploy to Google Cloud button, the Cloud Shell session doesn’t keep your files. Open the tutorial
again with the same project and state bucket, and enter every value in portward.auto.tfvars, including the ones
you’re not changing. Then run terraform apply. It replaces the machine, which means a minute or two of downtime. The
records disk and the key are kept.
- Add the
terraform output admin_connectoraddress in Claude or ChatGPT as a custom connector (Get started, step 7) and sign in. Members ofadmin_groups, and the people inadmin_subjects, are admins. If you’re refused, the gateway’s log (terraform output logs) names your id in an"admin connector refused"line: add it toadmin_subjectsand apply. - Add each database host to
setup_hosts, such as["db.internal.yourcompany.com"], and apply. Object stores, Microsoft 365 and Google Drive don’t use this list. - From the admin connector, set up the connection and choose what the AI may see: Get started, step 8.
- Say who may use the data connector: groups in
user_groups, or people inusers(sign-in ids, verified emails, or"@yourcompany.com"for everyone at your domain). Apply. - Each of them adds the
terraform output data_connectoraddress in Claude or ChatGPT: Get started, step 9.
Running it
Section titled “Running it”- Changing settings: edit the module’s values and
terraform apply, as in step 5. - Update, back up and restore: Update and back up. Google Cloud takes a snapshot of the records disk every day. Back up the gateway key once and keep it apart from the snapshots, as that page shows.
- Removing it: the key is protected from deletion. Set
keep_key_on_destroy = false,terraform apply, thenterraform destroy. Snapshots are kept until you delete them.
If something’s wrong
Section titled “If something’s wrong”| You see | Fix |
|---|---|
Serial console: waiting for secret ... | Add that secret (step 3). |
Serial console: couldn't pull | The license key is revoked or the trial ended. Replace the license key, or extend the trial. |
/health doesn’t answer over the relay | Check the license key is current (a reissued key replaces the old one: update license_key). |
/health doesn’t answer at your domain | DNS isn’t pointing at ip_address yet. |
| Sign-in says the redirect address doesn’t match | The directory app’s redirect address must be exactly terraform output redirect_uri. |