# Secrets on Cloud Run: Secret Manager as variables or files, who can read them, and rotating a key without an outage

> Secret Manager hands secrets to Cloud Run as variables, files or through a client. How each behaves on change, who needs which role, why build arguments leak, rotating a key in five steps.

Published 2026-10-12 · Updated 2026-10-13 · [By Exodus team](https://exodus.elim.io/about) · Operations
<https://exodus.elim.io/blog/secrets-on-cloud-run>

Secrets on Cloud Run live in Secret Manager, and Cloud Run hands them to the container in one of three ways: as an environment variable read once when an instance starts, as a mounted file fetched again on each read, or through the client library. The service's own account needs the Secret Accessor role on each secret, and each active version costs $0.06 a month after the first 6.

## In short

- Secret Manager bills $0.06 a month per active version after the first 6, and $0.03 per 10,000 reads after the first 10,000; the free allowance is shared by every project on the billing account.
- A rotation is 5 steps: add a version, deploy its number, check, disable the old one, destroy it a week later.
- A destruction delay of 1 to 1,000 days turns a mistaken destroy into a disable you can undo.
- A project may read secrets 90,000 times a minute, so code that uses the API keeps the value in memory rather than reading it on every request.
- In our test on Docker 29.3.0, a build argument and a deleted `.env` file stayed in the image; a BuildKit secret mount did not.

Prices are Google's list prices, read on October 12, 2026.

## Where a secret must not be

A database password, an API key, the key that signs session cookies: on a server they live in a `.env` file, on a platform in its dashboard, and on Cloud Run in Secret Manager. Four places they must not be:

**Plain environment variables.** `gcloud run deploy --set-env-vars DB_PASSWORD=...` works, and the value then sits in the service's configuration in clear text. Google's page on environment variables for jobs says it directly: they "are visible to anyone with project Viewer permissions or greater". The Cloud Run Viewer role alone can view the state of every Cloud Run resource, plain variables included. It has also been typed on a command line, so it is in your shell history.

**The image.** A value passed with `--build-arg`, or a `.env` file copied into the image and deleted in a later step, stays in the image's history or in a layer. Anyone who can pull the image can read it.

**The repository.** A `.env` committed once stays in the history after it is deleted.

**Logs.** A library that prints its configuration, or a debug page that dumps `process.env`, puts every environment variable into Cloud Logging. Google's best practices name this risk for secrets in environment variables.

Secret Manager removes the first three; the last one is up to your code.

## How Secret Manager is organised

A **secret** is a name in a project, such as `session-key`. It holds **versions**, numbered 1, 2, 3 and up, and each version is one value of up to 64 KiB. A version is enabled, disabled or destroyed. Enabled versions can be read; disabled ones cannot, but can be enabled again; destroyed ones are gone for good. `latest` is an alias for the newest enabled version.

A secret has a **replication policy**, chosen at creation and impossible to change later. Automatic replication, the default, keeps the value in several regions and is what Google recommends for most secrets. User-managed replication keeps it only in the regions you list, for rules about where data may be stored. Cloud Run does not support Secret Manager's separate regional secrets, so a secret that must stay in one region is a normal secret with user-managed replication to it.

Access is controlled with IAM on the secret itself. **Secret Accessor** (`roles/secretmanager.secretAccessor`) reads the value, and the service gets it. **Secret Version Manager** adds, disables and destroys versions, for whoever rotates. **Viewer** sees names and metadata, not values. Project Owner can read every secret; Google's access control page says Editor and Viewer cannot.

## Three ways to hand a secret to the container

![Secret Manager on the left with one secret and its versions 1, 2 and 3; three paths to the container on the right: an environment variable, read once when the instance starts and pinned to a version; a mounted file, fetched again on each read so latest follows a new version; and the client library, read whenever your code calls it; the container runs as a service account with Secret Accessor on the secret](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-935f389e83e8f56c2fcc21174cce3c3bc283056a946955f32d18a5ef90bd46a8-680x380-svg/bytes "Three ways in. The environment variable is fixed for the life of the instance; the file and the client library see a new version without a deploy.")

**As an environment variable.** Cloud Run reads the secret before it starts each instance and sets the variable. Your code reads `process.env.SESSION_KEY` as on any platform, which is why this is the usual choice when moving an app. The value is fixed for the life of the instance. If the read fails, because the version is disabled or the account lost access, the instance does not start. Google recommends pinning a version number here, so a new version reaches instances only through a deploy you can roll back.

**As a mounted file.** Cloud Run mounts the secret as a file, for example `/secrets/session-key`. Google's page says that when the volume is read, Cloud Run "always fetches the secret value from the Secret Manager to use the value with the latest version", which is what makes `latest` useful here: a new version reaches running instances the next time the code reads the file. Cloud Run does not check the secret when the instance starts, so a missing permission shows up later as a failed read. Code that reads the file once at startup and keeps the value behaves like an environment variable.

**Through the client library.** Your code calls Secret Manager itself, with the service's own identity and no key file:

```ts
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";

const client = new SecretManagerServiceClient();
const [version] = await client.accessSecretVersion({
  name: "projects/PROJECT_ID/secrets/session-key/versions/latest",
});
const key = version.payload.data.toString();
```

Google's best practices prefer this form in general, and it suits values that change while the app runs. Each read is a network call and a billed access operation, and a project is allowed 90,000 reads a minute, so keep the value in memory and refresh it on an interval rather than reading on every request.

## Step 1: give the service its own service account

If you deploy without `--service-account`, Cloud Run runs the service as the Compute Engine default service account. Google's service identity page recommends against it. Depending on the organisation's policy, that account may hold Editor on the whole project, shared by every service and VM that runs as it. Create one account per service:

```shell
gcloud iam service-accounts create api-runtime \
  --display-name="Runtime account of the api service"
```

The account starts with no roles; the steps below give it exactly the secrets it needs, and [IAM for a Cloud Run app](https://exodus.elim.io/blog/iam-for-a-cloud-run-app) covers the rest of its roles.

## Step 2: create the secret without putting it on a command line

Google's page on adding versions warns that a value given on the command line "appears as plaintext in the list of processes" and lands in the shell history. Give gcloud a file, or `-` for standard input:

```shell
# from a file you already have, readable only by you
gcloud secrets create session-key \
  --replication-policy=automatic \
  --data-file=session-key.txt

# or generate one and pipe it in, so it never touches the disk
openssl rand -base64 48 | tr -d '\n' | \
  gcloud secrets create session-key --data-file=-
```

The `tr -d '\n'` matters: `echo` and most generators end with a newline, and it becomes part of the secret: a [Stripe webhook secret](https://exodus.elim.io/blog/stripe-webhooks-on-cloud-run) stored that way rejects every event. To keep the value in one region, replace `--replication-policy=automatic` with `--replication-policy=user-managed --locations=europe-west1`; this is fixed once the secret exists.

Moving an app off another platform is also the moment to rotate the important values, because the old platform still holds them. [Moving from Heroku to Cloud Run](https://exodus.elim.io/blog/heroku-to-cloud-run) pipes its config vars into Secret Manager this way.

## Step 3: grant access on the secret, not on the project

```shell
gcloud secrets add-iam-policy-binding session-key \
  --member="serviceAccount:api-runtime@PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"
```

Granting the role on the project is one command instead of many, and it lets every service read every other service's secrets. Google's best practices recommend bindings on each secret. IAM changes propagate eventually rather than at once, so a deploy straight after a grant can still fail; wait and deploy again.

## Step 4: deploy with the secret

As an environment variable, pinned to version 1:

```shell
gcloud run deploy api --image=IMAGE_URL --region=europe-west1 \
  --service-account=api-runtime@PROJECT_ID.iam.gserviceaccount.com \
  --update-secrets=SESSION_KEY=session-key:1
```

As a file, following the newest version:

```shell
gcloud run deploy api --image=IMAGE_URL --region=europe-west1 \
  --service-account=api-runtime@PROJECT_ID.iam.gserviceaccount.com \
  --update-secrets=/secrets/session-key=session-key:latest
```

Several secrets go in one flag, separated by commas. The related flags differ:

- `--update-secrets` adds or changes the ones you name and keeps the rest.
- `--set-secrets` removes every existing secret first, as its `--help` says, then sets the list you give. A deploy script that uses it with a partial list silently drops the others.
- `--remove-secrets` and `--clear-secrets` take some or all away.

The same flags work on `gcloud run jobs deploy`. On every deploy Cloud Run checks that the service account can read each secret named, and the deploy fails if it cannot.

Two path rules for files. The part before the last `/` is a directory Cloud Run mounts, hiding whatever the image had there: mounting at `/app/config/key` hides the rest of `/app/config`. And two secrets cannot share a mount directory, so use `/secrets/db/password` and `/secrets/session/key`.

## Step 5: check what the service actually has

`gcloud run services describe` shows where each variable comes from. A plain variable shows its value; a secret shows only its name and version:

![Terminal output from our project with names masked: gcloud run services describe with a yaml format of the container's env shows a plain variable with its value and a SESSION_KEY variable taken from a secretKeyRef with key latest; gcloud secrets versions list shows version 1 enabled on 2026-10-11; gcloud secrets get-iam-policy shows one service account with roles/secretmanager.secretAccessor](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-8c6e5ec520310b79e42157b3c2ebee2967e955cbd23e97331e31fd1563b8f30b-680x460-svg/bytes "One of our services, read-only. The plain variable's value is in the service's configuration; the secret is only a reference. Each secret has one reader.")

Across our project, 7 of 8 services read secrets, each under its own service account, and each secret has one reader. Four mount a whole env file at `/secrets`; three take each secret as an environment variable. All use `latest`, against Google's advice for environment variables: a bad new version reaches the next instance that starts, with no deploy to roll back. That works for a small team that adds versions by hand and deploys right after; with automation adding versions, pin the number.

## One secret for a whole env file

An app that reads twenty variables from a `.env` can get twenty secrets or one. One secret holding the whole file, mounted as `/secrets/app.env`, is how four of our services work: the app loads it at startup, and a plain `ENV_FILE=/secrets/app.env` variable says where. The deploy command stays short and the billed versions few.

The price: every reader sees every value, the file must stay under 64 KiB, and changing one value means a new version of the whole file. Loaders such as dotenv read the file once, so a new version arrives when instances restart. Services that need different subsets get different files.

## Secrets at build time

Some builds need a secret, such as a token for a private package registry. A build argument is the obvious tool and the wrong one. Docker's own documentation says build arguments and environment variables "persist in the final image". We checked it with a fake token on Docker 29.3.0:

![Terminal output of the sample: a build with --build-arg API_TOKEN shows API_TOKEN=fake-token-3f9a in docker history; an image that copied .env and then removed it still has API_TOKEN=fake-token-3f9a in one of its layers; a build with --secret id=api_token reads the token during the build, and neither docker history nor any layer of the result contains it](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-131a1b080dad103cd4678e8452d2de56ddfe5d1aca684852df84c064aa743739-680x360-svg/bytes "A fake token passed three ways. The build argument and the deleted file stay in the image; the secret mount does not.")

The fix is a BuildKit secret mount. The Dockerfile reads the secret as a file during one `RUN` step, and nothing of it is written into the image:

```dockerfile
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN="$(cat /run/secrets/npm_token)" npm ci
```

Build it with `docker build --secret id=npm_token,src=npm_token.txt .` on your machine. In Cloud Build, a build config takes the value from Secret Manager with `availableSecrets`; a step lists the names it may use in `secretEnv` and refers to them as `$$NAME` inside `bash -c`. The build's service account needs Secret Accessor on that secret, like the service does. Google itself needs no stored secret in CI: [deploying from GitHub without keys](https://exodus.elim.io/blog/deploying-from-github-without-keys) signs in with a short-lived token.

Values that frameworks bake into the browser bundle, such as Next.js `NEXT_PUBLIC_` variables, are public by design and belong in the build, not in Secret Manager. The [Vercel guide](https://exodus.elim.io/blog/vercel-to-cloud-run) covers that case.

## Rotating a secret without an outage

![A rotation in five steps on a timeline: add version 2; deploy the service pinned to version 2, which replaces revision A that read version 1 with revision B that reads version 2; check logs and errors; disable version 1 and wait a week; destroy version 1, which is permanent; version 1 is enabled until step 4 and disabled until step 5, version 2 is enabled from step 1 on](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-6b069dd84d2a510980eb4aaf5e8d207bedc33adfb8063b664c33b29dc1a01e5c-680x320-svg/bytes "A rotation. Version 1 stays readable until nothing uses it, and disabled for a week before it is destroyed.")

A rotation replaces a value while the app keeps running. With versions pinned in environment variables, it goes like this:

1. **Add the new version.** `gcloud secrets versions add session-key --data-file=-`, with the new value piped in. Nothing reads it yet.
1. **Deploy the new version number.** `gcloud run services update api --region=europe-west1 --update-secrets=SESSION_KEY=session-key:2`. The new revision reads version 2; the old revision's instances keep version 1 until they shut down.
1. **Check.** Watch errors and logs for a while. If something breaks, [route traffic back](https://exodus.elim.io/blog/revisions-traffic-splitting-and-rollbacks) to the previous revision, which still reads version 1.
1. **Disable the old version.** `gcloud secrets versions disable 1 --secret=session-key`. Anything still reading it now fails, and you can turn it back on with `versions enable`. Google suggests waiting a week here before the next step.
1. **Destroy it.** `gcloud secrets versions destroy 1 --secret=session-key`. This is permanent, and a destroyed version stops being billed.

Two things make this safe in practice. First, for keys that sign or encrypt, the app has to accept the old key and the new one for a while, or every session and token signed with the old key fails at step 2. The usual pattern is a list, newest first: sign with the first, accept any; the [sessions guide](https://exodus.elim.io/blog/sessions-without-redis) shows it for cookies. Second, set a destruction delay on secrets that matter: `gcloud secrets update session-key --version-destroy-ttl=7d` makes a destroy request disable the version and destroy it only after 7 days (Google allows 1 to 1,000 days), so a mistaken destroy can be undone.

Two cautions about `latest`. Reading a new version by its number right after adding it is strongly consistent; reading through `latest` is not, and Google says it usually converges within minutes, sometimes hours. And Secret Manager's rotation schedules do not rotate anything. A schedule sends a `SECRET_ROTATE` message to a Pub/Sub topic at the set time, at most once an hour; making the new value, adding it and deploying is your code's job.

## Who read the secret

Adding, disabling and destroying versions go to the Admin Activity audit log, which is always written. Reading a value (`AccessSecretVersion`) is a Data Access log, and Cloud Audit Logs says Data Access logs "are disabled by default for all services but some BigQuery services". So by default you can see who changed a secret but not who read it. Google's best practices recommend turning on Data Access logs for Secret Manager in the project's audit configuration.

## What it costs

List prices of 2026-10-12, from Google's pricing page and the Cloud Billing Catalog API:

- **Active versions**: $0.06 a month each, after 6 free. Enabled and disabled versions are active; destroyed ones are free.
- **Reads** (access operations): $0.03 per 10,000, after 10,000 free a month.
- **Rotation notifications**: $0.05 each, after 3 free a month.
- **Creating, disabling, destroying and changing secrets**: free.
- **Replication**: an automatic secret is billed as one location; a user-managed secret is billed once per region in its list.

The free allowance belongs to the billing account, not the project. Our billing account has Secret Manager in 6 projects. From October 1 to 11, 2026, our project's versions cost $0.0576 because the other projects had already used the 6 free versions, and our 668 reads in 8 days (52 to 138 a day) cost nothing because the account's 3,362 reads stayed under 10,000.

Worked examples at those prices, assuming the billing account has no other secrets:

- **5 secrets, one version each, read when instances start**: $0.
- **20 secrets, one version each**: 14 billed versions, $0.84 a month. During a rotation, the old version adds $0.06 a month until it is destroyed.
- **One secret read through the API on every request, 1 million requests a month**: 990,000 billed reads, $2.97 a month. Read once per instance and keep it in memory, and it drops back to zero.

The quota matters more than the price: 90,000 reads a minute per project, and a deploy that starts many instances at once, each reading many secrets, is the "thundering herd" Google's best practices warn about.

## What breaks

- **The deploy fails with a permission error on a secret.** The service account lacks Secret Accessor on that secret, the grant has not propagated yet, or the service runs as a different account than you granted. Check `--service-account` in the deploy and the secret's policy.
- **Instances stop starting after a rotation.** An environment variable points at a version you disabled. Running instances keep going, so the failure shows up later.
- **The value has a newline at the end.** It was created with `echo` instead of `echo -n` or `printf`. Add a new version without it.
- **A deploy removed other secrets.** It used `--set-secrets` with a partial list.
- **A new version did not reach the app.** It was an environment variable, or the app read the file once at startup. Deploy, or restart instances.
- **Every service can read every secret.** Secret Accessor was granted on the project. Move the grants to the secrets.
- **A secret is readable in the console.** It was set with `--set-env-vars`. Move it to Secret Manager, and since it was exposed, rotate it.
- **The image has a token in it.** It came in as a build argument or a copied file. Rebuild with a secret mount, delete the old image, and rotate the token.

## Checklist

1. One service account per service, set with `--service-account`.
1. Every password, key and connection string with credentials in Secret Manager; plain variables only for values you would show a colleague.
1. Secret Accessor granted per secret, to the one account that reads it.
1. Environment variables pinned to version numbers; `latest` only for files the app re-reads.
1. Deploy scripts use `--update-secrets`, not `--set-secrets`, unless they list every secret.
1. No secrets in build arguments or copied files; BuildKit secret mounts or Cloud Build's `availableSecrets` instead.
1. Signing keys accepted as a list, so a rotation logs nobody out.
1. A destruction delay on the secrets that matter, and old versions disabled for a week before they are destroyed.
1. Data Access logs on for Secret Manager if you need to know who read a value.

## Where Exodus comes in

The Exodus CLI's audit reads the names of your environment variables, never their values, and never opens a `.env` file. Names that look like secrets (with `SECRET`, `_KEY`, `TOKEN` or `PASSWORD`, and database or broker URLs, which carry passwords) are counted, and the report lists them as Secret Manager secrets with a monthly cost line; the rest stay plain variables. The rules Exodus gives your coding agent say secret values are never printed or put on a command line, and IAM changes wait for your yes. The cost line counts the 6 free versions; if other projects on your billing account use them, add $0.06 a month per secret.

## FAQ

### Can a Cloud Run service pick up a new secret version without a deploy?

Yes, if the secret is a mounted file read through latest. Cloud Run fetches the value from Secret Manager each time the file is read, so a new version reaches running instances at the next read. A secret in an environment variable is read once when an instance starts, so it needs a deploy or new instances.

### Why does my Cloud Run deploy fail with a permission error on a secret?

On every deploy Cloud Run checks that the service account can read each secret named. The deploy fails when that account lacks the Secret Accessor role on the secret, when the grant has not propagated yet, or when the service runs as a different account than the one you granted.

### Who can see the environment variables of a Cloud Run service?

Anyone with Viewer on the project, and anyone with the Cloud Run Viewer role alone, can read plain environment variables in the service's configuration. A variable taken from Secret Manager shows only the secret's name and version, so passwords and keys belong there.

### How much does Secret Manager cost for a small Cloud Run app?

Usually nothing. The first 6 active versions and 10,000 reads a month are free per billing account, then $0.06 a month per version and $0.03 per 10,000 reads. Twenty secrets with one version each cost $0.84 a month; a secret read through the API on every request of 1 million costs $2.97.

## Sources

1. [Configure secrets for services, Cloud Run, Google (volume: the value is fetched on each read and works with rotation; environment variables resolved at instance startup, pin a version instead of latest; secrets checked at deploy; an unreadable env secret stops the instance from starting, a volume fails on read; Secret Accessor for the service identity; --update-secrets, --set-secrets clears existing ones, --remove-secrets, --clear-secrets; other projects by project number; no mounts at /dev, /proc, /sys; one secret per path; regional secrets not supported; a mount hides the files of an existing directory; updated Oct 7, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/configuring/services/secrets)
2. [Configure secrets for jobs, Cloud Run, Google (the same flags and checks for jobs; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/configuring/jobs/secrets)
3. [Configure environment variables for jobs, Cloud Run, Google ('environment variables are visible to anyone with project Viewer permissions or greater'; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/configuring/jobs/environment-variables)
4. [Configure environment variables for services, Cloud Run, Google (1,000 variables of up to 32 KB; --set-env-vars deletes the others; PORT reserved; never set GOOGLE_APPLICATION_CREDENTIALS with a service identity; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/configuring/services/environment-variables)
5. [Cloud Run IAM roles, Google (Cloud Run Viewer can view the state of all Cloud Run resources; updated Oct 8, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/reference/iam/roles)
6. [Introduction to service identity, Cloud Run, Google (a user-managed service account recommended; otherwise the Compute Engine default service account, which may be granted Editor automatically; updated Oct 7, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/securing/service-identity)
7. [Secret Manager pricing, Google (active versions, enabled or disabled, $0.06 a month after 6 free; destroyed versions free; access operations $0.03 per 10,000 after 10,000 free; management operations free; rotation notifications $0.05 after 3 free; free usage aggregated across projects by billing account; automatic replication billed as one location; read Oct 12, 2026)](https://cloud.google.com/secret-manager/pricing)
8. [Secret Manager quotas and limits, Google (payload 64 KiB; 90,000 access requests a minute per project; 600 read and 600 write requests a minute; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/quotas)
9. [Choose a secret replication policy, Secret Manager, Google (automatic is the default from gcloud and the console and recommended for most; locations cannot be changed later; user-managed for location requirements; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/choosing-replication)
10. [Secret Manager best practices, Google (least privilege with secret-level bindings; pin version numbers rather than latest; disable before destroy and wait a week; no expiration on production secrets; enable data access logs for AccessSecretVersion; plan quota for deploys and autoscaling; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/best-practices)
11. [Secret Manager access control, Google (Secret Accessor, Secret Version Manager, Viewer, Admin; Owner includes secretmanager.versions.access, Editor and Viewer do not; updated Oct 8, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/access-control)
12. [Manage access to secrets, Secret Manager, Google (gcloud secrets add-iam-policy-binding with roles/secretmanager.secretAccessor; read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/manage-access-to-secrets)
13. [Add a secret version, Secret Manager, Google (enabled, disabled and destroyed states; --data-file; a value on the command line shows in the process list and the shell history; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/add-secret-version)
14. [Access a secret version, Secret Manager, Google (latest; reading a new version by number is strongly consistent, latest and aliases are not; eventually consistent operations converge within minutes, sometimes hours; IAM changes propagate eventually; client library samples; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/access-secret-version)
15. [Disable and destroy secret versions, Secret Manager, Google (destroy is permanent, disable first; read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/destroy-secret-version)
16. [Delayed destruction of secret versions, Secret Manager, Google (1 to 1000 days; the version is disabled until then and can be restored; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/delay-destruction-of-secret-versions)
17. [Create rotation schedules, Secret Manager, Google (a SECRET_ROTATE message to Pub/Sub; your subscriber does the rotation; period at least 1 hour; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/secret-rotation)
18. [Secret Manager audit logging, Google (AccessSecretVersion is a Data Access log, AddSecretVersion an Admin Activity log; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/secret-manager/docs/audit-logging)
19. [Cloud Audit Logs overview, Google (Data Access audit logs are disabled by default for all services but some BigQuery services; read Oct 12, 2026)](https://docs.cloud.google.com/logging/docs/audit)
20. [Use secrets from Secret Manager, Cloud Build, Google (availableSecrets, secretEnv, $$NAME in args; Secret Accessor for the build's service account; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/build/docs/securing-builds/use-secrets)
21. [Build secrets, Docker docs (build arguments and environment variables persist in the final image; RUN --mount=type=secret; read Oct 12, 2026)](https://docs.docker.com/build/building/secrets/)
22. [Cloud Billing Catalog API, Secret Manager SKUs (access operations $0.000003 each after 10,000; version replica storage $0.06 a month after 6; rotate operations $0.05 after 3; europe-west1 included; read Oct 12, 2026)](https://docs.cloud.google.com/billing/docs/reference/rest/v1/services.skus/list)
23. [gcloud CLI reference, Google (run deploy --set-secrets, --update-secrets, --remove-secrets, --clear-secrets, --service-account; secrets create, versions add, access, disable, enable, destroy, add-iam-policy-binding, update --version-destroy-ttl; checked with --help on gcloud 587.0.0, Oct 12, 2026)](https://docs.cloud.google.com/sdk/gcloud/reference/secrets)
24. Exodus team: read-only look at our own project on Oct 12, 2026 (13 secrets in europe-west1, 12 with one enabled version; 7 of 8 services read secrets, each with its own service account bound to its own secrets; 4 mount an env file, 3 use environment variables, all at latest) and our billing export for October 1 to 11, 2026 (Secret Manager on 6 projects of one billing account, 668 of 3,362 access operations ours, our versions billed from the first because the 6 free ones were used by the other projects); a local Docker 29.3.0 run with a fake token: a build argument and a deleted .env file stay in the image, a BuildKit secret mount does not
