# How to Access OpenAI Models via Google Cloud: Step-by-Step Setup

> AI Release · @ai_release1 · https://ai-release.org/guides/kak-podklyuchit-modeli-openai-cherez-google-cloud-posha_en.html

## TL;DR

- **Time to complete:** 40–60 minutes
- **Difficulty:** Intermediate
- **You will need:** a Google Cloud project with billing enabled, the `gcloud` CLI initialized, Python 3, an OpenAI API key (starts with `sk-`), and the `roles/owner` or `roles/editor` role on the project plus `roles/secretmanager.admin`

Scope note: Google Cloud does not resell OpenAI API keys. The pattern below runs your workload inside Google Cloud and calls the OpenAI API from it, keeping the key in Secret Manager instead of source code or environment files. Whether a given OpenAI model is also offered as a managed endpoint in Vertex AI Model Garden changes over time — verify current partner-model availability against the documentation before choosing that path.

## Requirements and setup
![Illustration: Requirements and setup](https://ai-release.org/guides/img/kak-podklyuchit-modeli-openai-cherez-google-cloud-posha-en-1.jpg)


- [ ] OS: macOS, Linux, or Windows 10/11 with WSL2
- [ ] `gcloud` CLI installed and initialized (`gcloud version` must print a version string)
- [ ] A Google Cloud project with an active billing account attached
- [ ] IAM rights to enable APIs, create service accounts, and create secrets
- [ ] An OpenAI API key created in your OpenAI account dashboard
- [ ] Python 3 available (`python3 --version`) for the smoke test script
- [ ] ~100 MB of free disk for the CLI config and container build cache

**Example:** a working starting state looks like this.

```bash
$ gcloud version
Google Cloud SDK 5xx.x.x
$ gcloud config list
[core]
project = ai-openai-gw
account = you@example.com
```

**Why this works:** the `gcloud` CLI stores the active project and account per user, so every later command applies to the right project without repeating the `--project` flag.

## Step 1. Create the project baseline and enable the APIs

**What to do:** create (or select) the project, link billing, and enable the three APIs the rest of the guide depends on: Secret Manager, Cloud Run, and Cloud Build.

```bash
gcloud projects create ai-openai-gw --name="OpenAI via GCP"
gcloud config set project ai-openai-gw

# Find your billing account ID
gcloud beta billing accounts list

# Link it — replace the placeholder with the ID from the previous command
gcloud beta billing projects link ai-openai-gw \
  --billing-account=<BILLING_ACCOUNT_ID>

gcloud services enable \
  secretmanager.googleapis.com \
  run.googleapis.com \
  cloudbuild.googleapis.com
```

Placeholders: `<BILLING_ACCOUNT_ID>` is the value in the `ACCOUNT_ID` column, formatted `XXXXXX-XXXXXX-XXXXXX`. Replace `ai-openai-gw` with your own project ID if it is already taken — project IDs are globally unique.

**Why:** no Google Cloud API is callable until it is enabled on the project, and a project without a billing account rejects every billable call. Enabling only three services keeps the surface small and the audit log readable.

**How to check:** list what is actually enabled.

```bash
gcloud services list --enabled \
  --filter="config.name:(secretmanager.googleapis.com OR run.googleapis.com OR cloudbuild.googleapis.com)"
```

Expected output — three rows, each with `STATE: ENABLED`:

```
NAME                             TITLE
cloudbuild.googleapis.com        Cloud Build API
run.googleapis.com               Cloud Run Admin API
secretmanager.googleapis.com     Secret Manager API
```

If a row is missing, the enable call failed; rerun it and read the error text.

## Step 2. Store the OpenAI key in Secret Manager and grant access
![Illustration: Step 2. Store the OpenAI key in Secret Manager and grant access](https://ai-release.org/guides/img/kak-podklyuchit-modeli-openai-cherez-google-cloud-posha-en-2.jpg)


**What to do:** upload the OpenAI key as a secret version, create a dedicated service account, and bind only the Secret Accessor role to it.

```bash
# 1. Read the key into a shell variable without echoing it
read -rs OPENAI_API_KEY

# 2. Create the secret and its first version
printf '%s' "$OPENAI_API_KEY" \
  | gcloud secrets create openai-api-key \
      --data-file=- \
      --replication-policy=automatic

# 3. Create a least-privilege identity for the workload
gcloud iam service-accounts create openai-runner \
  --display-name="OpenAI runner"

# 4. Allow that identity to read this one secret
gcloud secrets add-iam-policy-binding openai-api-key \
  --member="serviceAccount:openai-runner@ai-openai-gw.iam.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"
```

Then deploy a container that receives the secret as a reference, not as a literal value:

```bash
gcloud run deploy openai-proxy \
  --image=<REGION>-docker.pkg.dev/ai-openai-gw/apps/openai-proxy:latest \
  --region=<REGION> \
  --service-account=openai-runner@ai-openai-gw.iam.gserviceaccount.com \
  --set-secrets=OPENAI_API_KEY=openai-api-key:latest
```

Replace `<REGION>` with a region you enabled, for example `us-central1`.

**Why:** `--set-env-vars OPENAI_API_KEY=sk-...` writes the literal key into the revision configuration, where anyone holding `roles/viewer` on the project can read it. `--set-secrets` stores only a reference (`openai-api-key:latest`), and the container fetches the value at runtime using the service account's identity. A dedicated service account means a compromised container can read exactly one secret and nothing else.

**How to check:** confirm the secret exists, has one version, and that the stored value looks like a key.

```bash
gcloud secrets versions list openai-api-key
gcloud secrets versions access latest --secret=openai-api-key | head -c 7
```

Expected: one row in `STATE: enabled`, then `sk-` (or `sk-proj` for project-scoped keys) with no trailing newline. If you see the key truncated mid-string or followed by a blank line, you uploaded it with `echo` instead of `printf` — recreate the version.

If you plan to route traffic to several providers behind one endpoint, the pattern in [How to combine multiple AI providers behind one OpenAI-compatible router](https://ai-release.org/guides/kak-obedinit-neskolko-ii-provayderov-v-odin-api-nastroy.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) extends this step cleanly: one secret per provider, one service account per deployment.

## Step 3. Verify the key end to end and lock down access
![Illustration: Step 3. Verify the key end to end and lock down access](https://ai-release.org/guides/img/kak-podklyuchit-modeli-openai-cherez-google-cloud-posha-en-3.jpg)


**What to do:** run two checks — one directly against the OpenAI API from your machine, one through the deployed Cloud Run service.

```bash
# Check A: the stored secret is a valid OpenAI credential
export OPENAI_API_KEY="$(gcloud secrets versions access latest --secret=openai-api-key)"
curl -sS https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY" | head -c 300
```

Expected output starts with `{"object":"list","data":[{"id":"...` — a JSON list of model IDs available to your account. Note the IDs from that list rather than hard-coding a model name in your app; availability depends on your OpenAI account tier.

```bash
# Check B: the deployed service is up and bound to the right identity
gcloud run services describe openai-proxy \
  --region=<REGION> \
  --format="value(status.url,status.conditions[0].status)"
```

Expected: the service URL followed by `True`. A `False` here means the revision never became ready — read the logs with `gcloud run services logs read openai-proxy --region=<REGION>`.

Then send one request through your service:

```bash
SERVICE_URL=<YOUR_SERVICE_URL>
curl -sS "$SERVICE_URL/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{"model":"<MODEL_ID_FROM_STEP_3_CHECK_A>",
       "messages":[{"role":"user","content":"Reply with the single word: ok"}]}'
```

**Why:** Check A isolates the credential from your infrastructure — if it fails, the problem is the key, not Google Cloud. Check B isolates the deployment from the credential. Running them in that order means a failure points at exactly one layer.

**How to check:** success looks like an HTTP 200 with a `choices` array in the body:

```json
{"id":"chatcmpl-...","object":"chat.completion",
 "choices":[{"index":0,"message":{"role":"assistant","content":"ok"},
 "finish_reason":"stop"}]}
```

A `403` from the Cloud Run URL means the service requires authentication — add `-H "Authorization: Bearer $(gcloud auth print-identity-token)"`.

For running OpenAI-compatible endpoints from a coding agent instead of `curl`, see [How to connect a local LLM to Claude Code: llama-server and an OpenAI-compatible API](https://ai-release.org/guides/kak-podklyuchit-lokalnuyu-model-k-claude-code-llama-ser.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) — the request shape is the same, only the base URL changes.

## Troubleshooting

**Problem. The service account cannot read the secret**

- Error: `ERROR: (gcloud.secrets.versions.access) PERMISSION_DENIED: Permission 'secretmanager.versions.access' denied for resource 'projects/ai-openai-gw/secrets/openai-api-key'.`
- Cause: the workload runs as a service account that has no binding on the secret, or the binding was added on the project instead of the secret.
- Solution: check the effective policy, then re-add the binding — note the email domain must match the project ID exactly.

```bash
gcloud secrets get-iam-policy openai-api-key
gcloud secrets add-iam-policy-binding openai-api-key \
  --member="serviceAccount:openai-runner@ai-openai-gw.iam.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"
```

**Problem. OpenAI rejects the key that works in the dashboard**

- Error: `{"error": {"message": "Incorrect API key provided: sk-...xxxx. You can find your API key at https://platform.openai.com/account/api-keys.", "type": "invalid_request_error", "param": null, "code": "invalid_api_key"}}`
- Cause: a trailing newline or carriage return captured into the secret — usually from `echo "$KEY" | gcloud secrets create ...` on Windows/WSL, or from copy-pasting a key with a line break.
- Solution: create a new version using `printf '%s'` and re-run the check.

```bash
printf '%s' "$OPENAI_API_KEY" | gcloud secrets versions add openai-api-key --data-file=-
gcloud secrets versions access latest --secret=openai-api-key | wc -c
```

The byte count must equal the key length exactly — no extra `1` for a newline.

**Problem. Requests fail with a quota error while the key is valid**

- Error: `{"error": {"message": "You exceeded your current quota, please check your plan and billing details.", "type": "insufficient_quota", "code": "insufficient_quota"}}` or HTTP `RESOURCE_EXHAUSTED` from Cloud Run.
- Cause: the OpenAI account behind the key has no credit or has hit its rate limit; Google Cloud billing does not pay for OpenAI usage.
- Solution: check usage and limits in the OpenAI account dashboard, and add retry with backoff in the caller instead of retrying in a tight loop.

**Example:** a minimal retry wrapper that only retries on quota/rate errors:

```python
import time, openai

def chat(client, **kwargs):
    for attempt in range(4):
        try:
            return client.chat.completions.create(**kwargs)
        except openai.RateLimitError:
            time.sleep(2 ** attempt)
    raise RuntimeError("quota still exhausted after 4 attempts")
```

## Summary and next steps

You now have the OpenAI key stored as a Secret Manager version, a least-privilege service account that can read only that secret, and a Cloud Run service verified end to end with a live chat completion. Next, add alerting on `secretmanager.versions.access` audit logs so you notice unexpected reads, and if you add a second provider, front both with the router guide linked above rather than duplicating credentials in each client.

## FAQ

**Do I need a Google Cloud account to use the OpenAI API?**
No. The key is issued by OpenAI and billed by OpenAI; Google Cloud is used here only to store the key securely and to host the code that calls it.

**Where should I put the API key in Google Cloud?**
In Secret Manager, mounted into the workload as a reference via `--set-secrets`. Never in `--set-env-vars`, a Dockerfile, or a committed `.env` file.

**Can I use several OpenAI keys for different environments?**
Yes — create one secret per environment (`openai-api-key-dev`, `openai-api-key-prod`) and bind each service account to exactly one of them. That way a staging compromise cannot spend production quota.

**How do I rotate the key without downtime?**
Add a new version with `gcloud secrets versions add openai-api-key --data-file=-`, redeploy or restart the service, then disable the old version with `gcloud secrets versions disable <VERSION> --secret=openai-api-key`. The `:latest` reference picks up the new version on the next cold start.
