Deploy on Google Confidential Space

Follow the steps from project setup and IAM to private networking, prebuilt images, gateway integration, and customer acceptance policies.

Applies to: current main branch implementationReviewed: 2026-08-27Estimated time: 45–90 minutes

Before you deploy

TrustedAIProxy runs at the gateway's egress. Only internal callers should reach its proxy port. End users do not connect to the proxy or install its internal MITM CA; they establish trust through response headers and a customer-accessible proof endpoint.

What you need

  • A Google Cloud project with billing enabled and an authenticated gcloud CLI;
  • Permissions to create Compute Engine, VPC, firewall, service account, and Secret Manager resources;
  • Access to pull the immutable TrustedAIProxy image specified here from GHCR;
  • Internal network connectivity between TAP and a gateway such as New API / One API;
  • Customer-approved project, instance, image digest, upstream, and attestation policies.

Official Google references: Deploy Confidential Space workloads, Image types and lifecycle, Supported hardware and regions.

1

Set deployment variables

This example uses AMD SEV with n2d-standard-2. First confirm that your zone supports the CPU. If you switch to TDX, update the machine type, zone, and customer-approved hwmodel together.

export PROJECT_ID="your-project"
export REGION="us-central1"
export ZONE="us-central1-a"
export INSTANCE_NAME="tap-01"
export MACHINE_TYPE="n2d-standard-2"
export SERVICE_ACCOUNT_NAME="tap-workload"
export SERVICE_ACCOUNT="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
export NETWORK="tap-vpc"
export SUBNET="tap-subnet"
export SUBNET_CIDR="10.40.0.0/24"
export INTERNAL_CALLER_CIDR="10.40.1.0/24"
export IMAGE="ghcr.io/tokenevol/trustedaiproxy@sha256:8271352838ac4db3647b4b028f6f6ddd67266d4a0114ada49db3980e5ed331ea"

gcloud config set project "${PROJECT_ID}"
gcloud compute zones describe "${ZONE}" \
  --format="value(availableCpuPlatforms)"
i
Replace environment placeholders

Replace PROJECT_ID, the zone, and the caller CIDR;IMAGE is already pinned to a pullable version. Deliver these values as part of the customer's out-of-band policy after deployment.

2

Enable the required APIs

gcloud services enable \
  compute.googleapis.com \
  confidentialcomputing.googleapis.com \
  iam.googleapis.com \
  logging.googleapis.com \
  secretmanager.googleapis.com

This guide uses a public GHCR image directly. You do not need Artifact Registry or additional registry IAM permissions to pull it.

3

Create a workload identity

The VM's service account must be able to request Confidential Space attestation tokens. Logging permissions are for test diagnostics; production images should not redirect application logs by default.

gcloud iam service-accounts create "${SERVICE_ACCOUNT_NAME}" \
  --display-name="TrustedAIProxy Confidential Space workload"

gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
  --member="serviceAccount:${SERVICE_ACCOUNT}" \
  --role="roles/confidentialcomputing.workloadUser"

gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
  --member="serviceAccount:${SERVICE_ACCOUNT}" \
  --role="roles/logging.logWriter"

gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT}" \
  --member="user:$(gcloud config get-value account)" \
  --role="roles/iam.serviceAccountUser"

Apply least privilege in production: remove logging.logWriter if you do not write to Cloud Logging. Grant Secret Manager read access to individual secrets, rather than the entire project.

4

Use a published immutable image

You do not need to build or publish an image yourself. Use the fixed digest already published to GHCR by this repository:

export IMAGE="ghcr.io/tokenevol/trustedaiproxy@sha256:8271352838ac4db3647b4b028f6f6ddd67266d4a0114ada49db3980e5ed331ea"

Pinning a digest prevents silent code changes when a tag moves. The image digest in the customer's verification policy must match this value exactly.

5

Set up private networking and restricted ingress

This example creates a custom VPC, private subnet, and Cloud NAT. The VM has no public IP but can pull public images and reach model providers. Only the gateway's CIDR may access TAP on 8080/tcp.

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

gcloud compute networks subnets create "${SUBNET}" \
  --network="${NETWORK}" \
  --region="${REGION}" \
  --range="${SUBNET_CIDR}" \
  --enable-private-ip-google-access

gcloud compute routers create tap-router \
  --network="${NETWORK}" \
  --region="${REGION}"

gcloud compute routers nats create tap-nat \
  --router=tap-router \
  --region="${REGION}" \
  --auto-allocate-nat-external-ips \
  --nat-all-subnet-ip-ranges

gcloud compute firewall-rules create tap-from-internal \
  --network="${NETWORK}" \
  --direction=INGRESS \
  --action=ALLOW \
  --rules=tcp:8080 \
  --source-ranges="${INTERNAL_CALLER_CIDR}" \
  --target-tags=tap

If the gateway is in another VPC, configure VPC Peering, a VPN, or another private connection first. Restrict INTERNAL_CALLER_CIDR to the actual callers. Do not create a 0.0.0.0/0 → tcp:8080 rule.

6

Prepare secrets with fixed versions

Single-replica tests can run without PostgreSQL. For production with multiple replicas, store the full PostgreSQL DSN in Secret Manager and pass only a resource name with a fixed numeric version. The service creates or migrates proof-related tables automatically.

gcloud secrets create tap-pg-dsn --replication-policy=automatic

printf '%s' 'postgres://tap:PASSWORD@postgres.internal:5432/tap?sslmode=verify-full' \
  | gcloud secrets versions add tap-pg-dsn --data-file=-

gcloud secrets add-iam-policy-binding tap-pg-dsn \
  --member="serviceAccount:${SERVICE_ACCOUNT}" \
  --role="roles/secretmanager.secretAccessor"

export PG_SECRET_VERSION="projects/${PROJECT_ID}/secrets/tap-pg-dsn/versions/1"
  • Do not use versions/latest;
  • Keep DSNs, passwords, and CA private keys out of Git, images, command-line arguments, and instance metadata;
  • Enable database TLS verification and restrict network sources;
  • The service currently reads only the PostgreSQL DSN directly from Secret Manager. A separate, controlled bootstrap must still be implemented for the production MITM CA.
7

Create a debug test instance

Use only synthetic data and the demonstration CA. Debug images allow SSH/root access;dbgstat is enabled, which production customer policies must reject.

gcloud compute instances create "${INSTANCE_NAME}-debug" \
  --project="${PROJECT_ID}" \
  --zone="${ZONE}" \
  --machine-type="${MACHINE_TYPE}" \
  --network="${NETWORK}" \
  --subnet="${SUBNET}" \
  --no-address \
  --confidential-compute-type=SEV \
  --maintenance-policy=MIGRATE \
  --shielded-secure-boot \
  --image-project=confidential-space-images \
  --image-family=confidential-space-debug \
  --service-account="${SERVICE_ACCOUNT}" \
  --scopes=cloud-platform \
  --tags=tap \
  --metadata="^~^tee-image-reference=${IMAGE}~tee-restart-policy=OnFailure~tee-container-log-redirect=true"

Inspect startup logs and the internal IP:

gcloud compute instances get-serial-port-output "${INSTANCE_NAME}-debug" \
  --zone="${ZONE}" --port=1

export TAP_INTERNAL_IP="$(gcloud compute instances describe "${INSTANCE_NAME}-debug" \
  --zone="${ZONE}" --format='get(networkInterfaces[0].networkIP)')"

echo "TAP internal address: http://${TAP_INTERNAL_IP}:8080"

Check the metadata endpoints from an allowed internal host:

curl --fail --silent --show-error \
  "http://${TAP_INTERNAL_IP}:8080/.well-known/http-attestation-key"

NONCE="$(openssl rand -hex 16)"
curl --fail --silent --show-error \
  "http://${TAP_INTERNAL_IP}:8080/.well-known/confidential-attestation?nonce=${NONCE}"
8

Create a production instance

Production requires a stable, securely provisioned MITM CA. Ed25519 signing keys are generated in memory for each process and bound to startup attestation. Ensure the container never logs message bodies, queries, credentials, tokens, or DSNs. If CA provisioning changes the image, update its pinned digest and the customer policy together.

gcloud compute instances create "${INSTANCE_NAME}" \
  --project="${PROJECT_ID}" \
  --zone="${ZONE}" \
  --machine-type="${MACHINE_TYPE}" \
  --network="${NETWORK}" \
  --subnet="${SUBNET}" \
  --no-address \
  --confidential-compute-type=SEV \
  --maintenance-policy=MIGRATE \
  --shielded-secure-boot \
  --image-project=confidential-space-images \
  --image-family=confidential-space \
  --service-account="${SERVICE_ACCOUNT}" \
  --scopes=cloud-platform \
  --tags=tap \
  --metadata="^~^tee-image-reference=${IMAGE}~tee-restart-policy=OnFailure~tee-env-TAP_PG_DSN_SECRET_VERSION=${PG_SECRET_VERSION}"
i
Single replica without PostgreSQL

Remove tee-env-TAP_PG_DSN_SECRET_VERSION=… from metadata. The customer verifier must use --without-postgres and account for the lack of cross-replica routing for new challenges.

Production Confidential Space images disable SSH. Use minimal startup logs, internal health checks, and customer proof verification for operations. Do not switch a production instance back to a debug image for troubleshooting.

9

Configure your gateway to use TAP

Configure the proxy in the New API / One API gateway process and add the production Intermediate CA certificate to its system or application trust store. Only the internal gateway should trust this CA; end users must never be asked to install it.

export HTTP_PROXY="http://${TAP_INTERNAL_IP}:8080"
export HTTPS_PROXY="http://${TAP_INTERNAL_IP}:8080"
export NO_PROXY="127.0.0.1,localhost,metadata.google.internal"

# Import the production Intermediate CA as required by the gateway runtime.
# Do not disable TLS verification or use InsecureSkipVerify.

Confirm that the gateway returns all X-Attestation-* headers unchanged. The proxy clears forged or stale upstream headers with these names and adds new values only after successfully generating a local attestation.

10

Production acceptance checklist

CheckAcceptance criteria
ImageDeploy with @sha256:… and a digest that exactly matches the customer-approved value.
RuntimeThe Google token must contain swname=CONFIDENTIAL_SPACE, dbgstat=disabled-since-boot, secboot=true and satisfy hardware and STABLE/LATEST policies.
Key materialNo production CA private keys, signing private keys, DSNs, or credentials in images, Git, metadata, or logs.
NetworkNo public ingress; port 8080 accepts only the gateway's private CIDR; outbound TLS verification remains enabled.
Non-streaming signaturesSupported plain-text requests return llm-conversation-text-v1 and verify with the reference client.
Fail closedOversized, ambiguous, multimodal, or unsupported content is forwarded without locally generated attestation headers.
Streaming boundarySSE with a valid challenge returns only llm-request-upstream-v1; the product UI explicitly marks the body as unverified.
Multiple replicasproof_ref routes to its owner; offline owners, unknown references, and timeouts return the expected statuses.

Finally, deliver the issuer, audience, image digest, project ID/number, zone, instance allowlist, service account, hardware type, allowed domains/paths/models, and fixed secret version through a trusted out-of-band channel. Customers must not learn these expected values from the response they are trying to verify.

Troubleshooting

SymptomCheck first
Container does not startIs the GHCR package public? Does the digest exist? Are Cloud NAT and DNS working? Is the image built for linux/amd64?
Proof endpoint returns 503Does the service account have roles/confidentialcomputing.workloadUser? Is the launcher socket accessible? Are the audience and nonce valid?
Missing attestation headersCheck the request path, Content-Type, body size, text extractor, upstream TLS, and stream/challenge requirements for the profile.
Gateway request fails TLSConfirm that the gateway process trusts the correct internal CA, rather than an old CA. Do not disable certificate verification.
proof_ref lookup returns 404/410/503404 = unknown reference; 410 = owner offline; 503 = cross-replica issuance timeout or temporary failure. Check PostgreSQL and owner heartbeats.
Customer verification failsHas the gateway changed the model, path, roles, order, message boundaries, or text? Were headers dropped or merged? Has the nonce already been consumed?
Next: give customers independent verificationCustomers need out-of-band policy, proof bundles, and API responses, rather than a proxy address or internal CA.
User guide