> For the complete documentation index, see [llms.txt](https://docs.p0.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.p0.dev/integrations/resource-integrations/custom-application/deploying-a-connector-to-google-cloud-run.md).

# Deploying a connector to Google Cloud Run

Build the P0 connector SDK example, deploy it privately to Google Cloud Run, and register it in P0 so requestors can get just-in-time access to your own application.

This guide walks through deploying the `gcp-cloudrun` example from the [connector repository](https://github.com/p0-security/connector) to Google Cloud Run and connecting it to P0 Security.

{% hint style="info" %}
The custom application integration is in preview. Contact P0 if you'd like to build a connector for one of your internal applications.
{% endhint %}

## What you're deploying

A custom application connector is a small service that you write and host in your own cloud account. It implements six actions: `validateUser`, `userExists`, `createUser`, `deleteUser`, `setPoliciesForUser`, and `list`. P0 calls `validateUser` to check whether it may act on a principal at all, `userExists` to check whether it already has an account for them, `list` to learn which policies a requestor can ask for, and `createUser`, `deleteUser`, and `setPoliciesForUser` to provision, update, and remove access when a grant starts, changes, or ends.

This guide uses the `gcp-cloudrun` example included in the SDK (`packages/connector-sdk/examples/gcp-cloudrun`) as a starting point. It's a complete, deployable connector that you customize to call your own application instead of its placeholder logic.

### Prerequisites

* A Google Cloud project you can deploy to, with the Cloud Run and Cloud Build APIs enabled.
* The `gcloud` CLI installed and authenticated to that project.
* Node.js 24 or later to build the example locally, and Docker if you build the image yourself instead of using `gcloud run deploy --source`.
* An installed P0 Google Cloud integration. P0 invokes your Cloud Run service through this integration, so its service account is the identity that reaches your connector. If you don't have it yet, install it first from the [Google Cloud](/integrations/resource-integrations/google-cloud.md) page by providing your **Organization ID**.

<figure><img src="https://3783273641-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2Fgit-blob-551c35c959d18a38a3deb65d51b08bf24f41ae85%2Fgoogle_custom_app_connector_org_id.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

* Admin access to your P0 workspace, to register the connector once it's deployed.

## Part 1: Build the connector

1. Clone the connector SDK repository and copy the example into your own repository:

   ```bash
   git clone --depth 1 https://github.com/p0-security/connector.git p0-connector && cp -r p0-connector/packages/connector-sdk/examples/gcp-cloudrun my-connector && rm -rf p0-connector
   cd my-connector
   ```
2. Implement your actions in `src/actions.ts`. Replace the placeholder logic with calls into your own application's API.
3. Install dependencies and build:

   ```bash
   yarn install
   yarn run build
   ```

## Part 2: Deploy to Cloud Run

You can deploy straight from source with `gcloud run deploy --source .`, which builds the image for you, or build the container image yourself first. Either way, the service must run privately. P0 authenticates to it with a signed identity token, not over a public endpoint.

Four settings control whether P0 can find and call the connector. `INVOKER_SA_EMAIL` and the two IAM grants all take the same service account: the one from your P0 Google Cloud installation that P0 uses to invoke your connector. If you don't know which service account that is, contact P0.

| Setting                                   | Purpose                                                                                                                                                                                                               |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVOKER_SA_EMAIL` environment variable   | The service account P0 uses when it calls your connector. The connector checks every request's signed identity token against this value and rejects anything that doesn't match. This is the application-level check. |
| `CONNECTOR_AUDIENCE` environment variable | The service's own URL. The connector accepts only identity tokens issued for this exact URL. Cloud Run assigns the URL when you first deploy, so you set this in two steps below.                                     |
| `roles/run.invoker` IAM grant             | Grants that same service account permission to reach the private Cloud Run service at all. This is the network-level boundary.                                                                                        |
| `roles/run.viewer` IAM grant              | Lets P0 read the service's `run.app` URL when you register the connector. P0 signs its identity tokens for that URL.                                                                                                  |

If `INVOKER_SA_EMAIL` or `CONNECTOR_AUDIENCE` is unset, the connector refuses to start.

### Deploy with a placeholder audience

Deploy with `CONNECTOR_AUDIENCE` set to a placeholder, `set-after-first-deploy`. The connector starts and rejects every request until you replace the placeholder with the service URL in the next step.

#### Option A: Deploy from source

```bash
gcloud run deploy p0-custom-app-connector \
  --source . \
  --region <your-region> \
  --no-allow-unauthenticated \
  --set-env-vars INVOKER_SA_EMAIL=<p0-service-account-email>,CONNECTOR_AUDIENCE=set-after-first-deploy
```

#### Option B: Build and push the image yourself

```bash
docker build --platform linux/amd64 -t <your-image-tag> .
docker push <your-image-tag>

gcloud run deploy p0-custom-app-connector \
  --image <your-image-tag> \
  --region <your-region> \
  --no-allow-unauthenticated \
  --set-env-vars INVOKER_SA_EMAIL=<p0-service-account-email>,CONNECTOR_AUDIENCE=set-after-first-deploy
```

{% hint style="warning" %}
Pass `--platform linux/amd64` when you build on Apple silicon. Cloud Run runs `linux/amd64`, and an arm64 image fails at startup with an exec format error.
{% endhint %}

### Set the connector audience

1. Read the URL Cloud Run assigned to the service:

   ```bash
   gcloud run services describe p0-custom-app-connector \
     --region <your-region> \
     --format='value(status.url)'
   ```

   <figure><img src="https://3783273641-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2Fgit-blob-e30e20ccbb0fac1fa9a906fdfb25440e73ab254a%2Fgoogle_custom_app_connector_service_details.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>
2. Set `CONNECTOR_AUDIENCE` to that URL, exactly as printed:

   ```bash
   gcloud run services update p0-custom-app-connector \
     --region <your-region> \
     --update-env-vars CONNECTOR_AUDIENCE=<service-url>
   ```

   Use `--update-env-vars`, not `--set-env-vars`. `--set-env-vars` replaces every variable on the service, which removes `INVOKER_SA_EMAIL`.

The URL stays the same across deploys. When you deploy a new build later, leave out `--set-env-vars` so the service keeps both variables, or pass the service URL as `CONNECTOR_AUDIENCE` instead of the placeholder.

{% hint style="info" %}
Connectors built on `@p0security/connector-sdk` 0.0.5 or later require `CONNECTOR_AUDIENCE`. To upgrade a connector that's already deployed, run both steps above first, then deploy the new build without `--set-env-vars`. Earlier versions ignore the variable, so the running connector keeps serving while you upgrade.
{% endhint %}

### Grant P0 access to the service

Grant the same P0 service account permission to invoke the service and to read it:

```bash
gcloud run services add-iam-policy-binding p0-custom-app-connector \
  --region <your-region> \
  --member="serviceAccount:<p0-service-account-email>" \
  --role="roles/run.invoker"

gcloud run services add-iam-policy-binding p0-custom-app-connector \
  --region <your-region> \
  --member="serviceAccount:<p0-service-account-email>" \
  --role="roles/run.viewer"
```

### Put your own load balancer in front of the service

Skip this section if P0 can call the service's `run.app` URL directly.

If the connector has to answer at your own host name, with your own certificate, put a global external Application Load Balancer in front of the Cloud Run service and give P0 the load balancer's address when you register in Part 3. Google's [guide to load balancing serverless backends](https://docs.cloud.google.com/load-balancing/docs/https/setup-global-ext-https-serverless) walks through the load balancer itself. The steps below are the choices P0 depends on.

1. **Create a serverless network endpoint group** for the Cloud Run service and make it the backend service's only backend. Route every path to it, or at least `/trpc/*`. P0 calls the connector under `/trpc/`.
2. **Use a certificate from a public certificate authority.** P0 doesn't trust private authorities.
3. **Leave Identity-Aware Proxy off** on the backend service. P0's requests carry their own identity token, and IAP would reject it.
4. **Close the `run.app` URL to the internet**, so that traffic reaches the service only through the load balancer:

   ```bash
   gcloud run services update p0-custom-app-connector \
     --region <your-region> \
     --ingress internal-and-cloud-load-balancing
   ```

   P0 reads the service through the Cloud Run API, which this setting doesn't affect.
5. **Check the path before you register.** A request with no token should get a `403` from Cloud Run, which shows that the load balancer reached the service:

   ```bash
   curl -i https://<your-host>/trpc/app.metadata.get
   ```

   A `404` means the load balancer isn't routing to the service. A certificate error means the certificate isn't active yet. A Google-managed certificate can take up to an hour to activate after your DNS record points at the load balancer.

Leave `CONNECTOR_AUDIENCE` set to the `run.app` URL. P0 signs its identity tokens for that URL whichever address it calls, and Cloud Run accepts them through the load balancer without any further configuration. Turn on request logging on the backend service if you want to watch P0's calls arrive.

## Part 3: Register the connector in P0

Register the connector as a custom application in P0, following [Custom application](/integrations/resource-integrations/custom-application.md#install-a-custom-application). Select **Google Cloud Run** hosting and enter your project, connector name, and region. P0 looks up the service's `run.app` URL from these values, so you don't paste the service URL in.

If you put your own load balancer in front of the service, enter its address in **Connector URL**, for example `https://connector.example.com`. Enter only `https://`, the host name, and an optional port, with no path or trailing slash. P0 then sends every request to that address, including the check it runs when you complete registration. Leave **Connector URL** empty to have P0 call the `run.app` URL.

Keep `CONNECTOR_AUDIENCE` set to the `run.app` URL even when you enter a Connector URL. P0 always requests identity tokens for the `run.app` URL, whichever address it calls.

As part of registration, P0 shows ready-to-run `gcloud` commands, with its invoker service account already filled in, that grant that account `roles/run.invoker` and `roles/run.viewer` on your service. Run them if you haven't already, and use the same service account for `INVOKER_SA_EMAIL` in Part 2.

<figure><img src="https://3783273641-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2Fgit-blob-e1358ba966db1023f402153c40fcb25088fbdc78%2Fgoogle_custom_app_connector_grant_invoker.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

When you complete registration, P0 checks that it can authenticate to and reach the connector, so `INVOKER_SA_EMAIL`, `CONNECTOR_AUDIENCE`, and both IAM bindings must be in place before this step. Once the check passes, the connector's entitlements appear in P0's request-access picker, and access requests provision, update, and revoke users through the actions you implemented.

## Verify the install

* Request access to one of your entitlements as a test user and confirm the grant provisions correctly. For the requestor's side of this flow, see [Requesting access](/access-management/access-management/just-in-time-access/requesting-access.md).
* Let the grant expire, or revoke it manually, and confirm `deleteUser` and `setPoliciesForUser` behave as expected in your application.
* Check the structured logs the SDK emits per request. Each is tagged with the route and request ID, which is the fastest way to trace a specific grant:

  ```bash
  gcloud run services logs read p0-custom-app-connector --region <your-region>
  ```

## Troubleshooting

| Symptom                                                                                  | Likely cause                                                                                                                                                                                                       |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| P0 reports it is not permitted to read the Cloud Run service                             | `roles/run.viewer` isn't granted to P0's service account on the service.                                                                                                                                           |
| P0 reports the connector is unreachable at install time                                  | `INVOKER_SA_EMAIL` doesn't match the account P0 uses, `CONNECTOR_AUDIENCE` still holds the placeholder or doesn't match the service URL, or `roles/run.invoker` isn't granted to that account.                     |
| P0 reports the connector is unreachable at install time, and you entered a Connector URL | The load balancer doesn't route to the Cloud Run service, or `CONNECTOR_AUDIENCE` is set to your load balancer's address instead of the `run.app` URL.                                                             |
| The Cloud Run revision fails to start right after a deploy                               | `INVOKER_SA_EMAIL` or `CONNECTOR_AUDIENCE` is unset or empty, or `CONNECTOR_AUDIENCE` has leading or trailing whitespace.                                                                                          |
| The Cloud Run revision fails to start with an exec format error                          | The image was built for arm64, for example on Apple silicon, without `--platform linux/amd64`.                                                                                                                     |
| Every request returns `403`                                                              | The identity-token check is failing. Recheck `INVOKER_SA_EMAIL` against the service account from your P0 Google Cloud installation, and `CONNECTOR_AUDIENCE` against the output of `gcloud run services describe`. |
| A grant provisions, but access doesn't work in your application                          | Check the `createUser` and `setPoliciesForUser` logs. The connector may be accepting a policy key your application doesn't recognize.                                                                              |

## Related links

* [Custom application](/integrations/resource-integrations/custom-application.md), the integration overview and install steps
* [Deploying a connector to AWS Lambda](/integrations/resource-integrations/custom-application/deploying-a-connector-to-aws-lambda.md), the same install for an AWS Lambda connector
* [Deploying a connector with Terraform](/integrations/resource-integrations/custom-application/deploying-a-connector-with-terraform.md), if you manage your cloud infrastructure as code
* [Google Cloud](/integrations/resource-integrations/google-cloud.md), which P0 uses to invoke your connector
* [Requesting access](/access-management/access-management/just-in-time-access/requesting-access.md)
