gcloud auth login
gcloud config set project my-project
gcloud services enable secretmanager.googleapis.comYour API key stops living in your source code and starts living in Secret Manager.
Secret Manager is a Google Cloud service that keeps API keys and passwords out of your code, for only the apps you allow.
You need
Why not keep the key in your code?
One thing to rememberCode holds the name of the secret. Google holds the value.
- Code gets copied.Every clone, branch and build log carries the key. Deleting the line later does not remove it from git history.
- Changing the key gets easy.You store the new value in one place and point the app at it. No code change.
- You choose who can read it.Only the apps you name get the value.
The 20-second summary
- A secret is a named box.It has a name and a list of who may read it. On its own it holds no value.
- Each value is a numbered version.Adding a value creates version 1, then 2, then 3. A version never changes once it exists.
- Apps should name a version number.
latestis a label that moves to the newest version. Why that bites. - You choose who can read it.Your app gets its own identity. You give that identity read access to this one secret.
Nine words you will meet
- Project
- The Google Cloud container for your resources, permissions and bill.
- gcloud
- Google Cloud’s command-line tool. Cloud Shell, the terminal in the Cloud console, has it.
- IAM
- Identity and Access Management, Google Cloud’s permission system: who can do what to which resource.
- Service account
- An identity for software, not a person. Your app runs as one, and you grant it permissions.
- Cloud Run
- Google’s service for running your app in containers. The examples use it.
- Client library
- Google’s code package for calling Secret Manager from your app, such as
google-cloud-secret-managerfor Python. - Version
- One stored value of a secret, numbered 1, 2, 3. It never changes after you add it.
latest- A label for the most recently created version. How it can fail.
- Audit log
- Google Cloud’s record of who did what, and when.
A secret holds versions. Versions never change.
One thing to rememberAdding a value never edits the old one. It creates a new numbered version.
Each value you add becomes its own numbered version, fixed from then on.
Version details — sample data
- Name
- —
- State
- —
- Created
- —
- Payload
- —
How latest works
latest is a label, not a version. It points at the most recently created version. It will never fall back to an older version: if the newest one is disabled or destroyed, the read fails.
For production, pin a number, such as versions/3. It only changes when you change it.
Move one key, start to finish
One thing to rememberSign in, create, add the value, let one app read it, connect, check.
Swap api-key, my-project and my-service for your own names.
Sign in and pick your project
Terminalgcloud initdoes the first two in one step. The last line turns Secret Manager on for your project. You also need a role that can create secrets, such asroles/secretmanager.admin. If you created the project, you are its Owner and have both.Create the secret
Terminalgcloud secrets create api-key \ --replication-policy=automaticThis makes an empty, named secret.
automaticlets Google choose where copies live. You cannot change that later; see the fine print.Add the value
Terminalread -rs API_KEY # paste the key, press Enter; it stays hidden printf '%s' "$API_KEY" | gcloud secrets versions add api-key --data-file=- unset API_KEYThis stores version 1. Use
printf '%s', notecho.echoadds a newline, and Secret Manager stores it as part of the value. The service you call can then reject the key, which shows up as an auth failure. Google’s docs useecho -nfor the same reason.Let your app read it
Terminalgcloud iam service-accounts create app-runtime gcloud run services update my-service \ --service-account=app-runtime@my-project.iam.gserviceaccount.com gcloud secrets add-iam-policy-binding api-key \ --member="serviceAccount:app-runtime@my-project.iam.gserviceaccount.com" \ --role="roles/secretmanager.secretAccessor"Your app runs as a service account. Unless you pick one, Cloud Run uses the Compute Engine default,
PROJECT_NUMBER-compute@developer.gserviceaccount.com. Google recommends a dedicated one: create it, attach it (a new revision, or do it at deploy), then grant it this one secret.Connect your app: three ways
- Environment variable. The default. Cloud Run reads the secret before each instance starts and sets
API_KEY. - Mounted file. Cloud Run shows the secret as a file and reads it from Secret Manager each time your code reads the file.
- Client library. Your code asks Secret Manager itself, through the client library. Use it off Cloud Run.
Environment variable: copied in at startup
Client library: read when your code asks
Cloud Run
gcloud run services update my-service \ --update-secrets=API_KEY=api-key:1Pins
API_KEYto version 1. This command deploys a new revision: a new release of your service. For a file, use a path:/secrets/api/key=api-key:1.Python
from google.cloud import secretmanager client = secretmanager.SecretManagerServiceClient() name = "projects/my-project/secrets/api-key/versions/1" api_key = client.access_secret_version(name=name).payload.data.decode("UTF-8")Install
google-cloud-secret-managerfirst. It names version 1, the same pin. The value arrives as bytes, so decode it, and cache it.- Environment variable. The default. Cloud Run reads the secret before each instance starts and sets
Check it worked
Terminalgcloud secrets versions list api-keyYou should see version 1, enabled. Then try the feature that uses the key; if it fails, check the logs.
Last, delete the key from your code. Git history still has it, so treat it as leaked and rotate it.
Who can read it
One thing to rememberGrant roles/secretmanager.secretAccessor on the one secret an app needs, never across the whole project.
Every read is checked against the secret’s IAM permission list.
Try a caller
Pick a caller to see whether the request is allowed.
| Time | Principal | Method | Result |
|---|
No requests yet.
Each read is an AccessSecretVersion call. Data Access audit logs, which record reads, are off by default. Enable them to see who read a secret.
secretAccessor reads the value. What a running app needs, granted on one secret.
viewer sees details, not values. Names, versions and states only.
secretVersionManager rotates without reading. It adds, disables and destroys versions. Good for a rotation job.
admin does everything, including permissions. For people who set secrets up, never apps.
Rotate safely
One thing to rememberAdd the new key before you remove the old one. Disable first. Destroy last.
Swap in a new key with no downtime. Only the last step cannot be undone.
- Step 1 of 6
Get a new key from the provider
Create a second key where you got the first. Leave the old one working for now.
- Step 2 of 6
Add it as a new version
Version 2 appears. Version 1 is untouched, and the app keeps reading it because it is pinned to 1.
Commandread -rs NEW_KEY printf '%s' "$NEW_KEY" | gcloud secrets versions add api-key --data-file=- unset NEW_KEY - Step 3 of 6
Point the app at it
Change the pin to version 2. This command deploys a new revision, and new instances start with the new key.
Commandgcloud run services update my-service \ --update-secrets=API_KEY=api-key:2 - Step 4 of 6
Check that it works
Use the feature that needs the key and watch for errors. If it fails, set the pin back to 1. Nothing is lost.
Commandgcloud run revisions list --service=my-service - Step 5 of 6
Disable the old version
Reads of version 1 now fail, which flushes out anything still using it. Watch for a few days.
enablebrings it back.Commandgcloud secrets versions disable 1 --secret=api-key - Step 6 of 6
Revoke the old key, then destroy it
Revoke the old key at the provider first. That kills every copy, including the one in git history. Then destroy version 1.
Commandgcloud secrets versions destroy 1 --secret=api-keyDestroy is immediate and permanent by default
Its number is never reused. Only a destruction delay set on the secret before you destroy lets you restore it; see the fine print.
All commands
One thing to rememberNothing new here. It is every command from this page, plus a few for cleanup.
More commands
Sign in and enable the API
gcloud auth login
gcloud config set project my-project
gcloud services enable secretmanager.googleapis.comOne-time setup. Step 0.
Create a secret
gcloud secrets create api-key --replication-policy=automaticAn empty, named secret. Step 1.
Add a version
printf '%s' "$API_KEY" | gcloud secrets versions add api-key --data-file=-
gcloud secrets versions add api-key --data-file=./api-key.txtFrom a variable or a file. Files often end in a newline too. Step 2.
Let one app read it
gcloud iam service-accounts create app-runtime
gcloud run services update my-service --service-account=app-runtime@my-project.iam.gserviceaccount.com
gcloud secrets add-iam-policy-binding api-key \
--member="serviceAccount:app-runtime@my-project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"Create, attach, then grant on this one secret only. Step 3.
Pin it on Cloud Run
gcloud run services update my-service --update-secrets=API_KEY=api-key:1Deploys a new revision. Step 4.
List secrets and versions
gcloud secrets list
gcloud secrets versions list api-keyNumbers, states and dates. Never values.
Read a value into a file
gcloud secrets versions access 1 --secret=api-key --out-file=./api-key.txtWithout --out-file, the value prints to your terminal and its scrollback.
Disable and re-enable
gcloud secrets versions disable 1 --secret=api-key
gcloud secrets versions enable 1 --secret=api-keyReversible. Rotate, step 5.
Set a delay, then destroy
# set this before you destroy
gcloud secrets update api-key --version-destroy-ttl=86400s
gcloud secrets versions destroy 1 --secret=api-keySet this before you destroy: the one-day delay only covers versions destroyed after it is on. Fine print.
Delete the whole secret
gcloud secrets delete api-keyRemoves every version and the permission list, for good. For retiring a secret, not rotating it.
Common mistakes
One thing to rememberA secret is not a file you edit. It is a list of versions you add to.
echo stores a newline
Use printf '%s'. Why, in step 2.
Pin a version number
A pin like api-key:2 changes only when you move it. How latest works.
Grant access on one secret
A project-level grant opens every secret in the project. Bind it on one secret.
The pin decides what the app reads
A pinned app keeps its version until you move the pin. Rotate safely.
Git history keeps the key
Anyone with the repository can find it. Revoke it at the provider (step 6).
More mistakes
Disable is reversible. Destroy is not.
Disable, watch for failures, and destroy last. Rotate safely.
A logged secret is a leaked secret
Log the version name, such as api-key/versions/3, never the value.
Each read costs a round trip
Uncached reads add latency and are billed after the free allowance. Cache the value. Fine print.
Delete removes the history too
It takes every version and the permission list with it. Rotate by adding a version.
Replication is fixed at creation
Changing it means a new secret. Fine print.
Schedules only send a reminder
They publish a Pub/Sub message on time. Your own code has to do the rotation.
Hand it to your coding assistant
Paste this into Claude Code, Cursor or another coding assistant. It applies this guide to your repository and forbids showing any part of a secret.
Help me move hardcoded credentials in this repository into Google Cloud Secret Manager without exposing any secret value.
RULES
1. Show me the full plan before edits, and wait for my approval.
2. Never print a credential, not even part of one. Report each finding as file:line, the kind of secret, and a label that contains no characters from the value.
3. Never fetch or echo a secret payload into this conversation. Do not run gcloud secrets versions access just to show me a value.
4. No destructive operations and no history rewrites. Do not delete secrets or versions, do not force push, and do not rewrite git history.
5. Stop and ask if the project, the service identity, the rollback plan or my approval is missing.
STEPS
1. Inventory, read-only: search source, config, .env* files, deployment manifests, CI settings, build arguments, scripts and docs.
2. Confirm the Google Cloud project with me, then enable the API:
gcloud services enable secretmanager.googleapis.com
3. For each secret, propose a kebab-case name, then create it and add the value:
gcloud secrets create NAME --replication-policy=automatic
printf '%s' "$VALUE" | gcloud secrets versions add NAME --data-file=-
Use printf '%s', not echo: echo adds a newline that becomes part of the stored value.
4. Grant roles/secretmanager.secretAccessor on that one secret only, never project-wide, and only to the service account that runs the app:
gcloud secrets add-iam-policy-binding NAME \
--member="serviceAccount:SERVICE_ACCOUNT" \
--role="roles/secretmanager.secretAccessor"
5. Change the code to read the value from an environment variable, a mounted secret file, or the Secret Manager client library. No hard-coded fallback: if the value is missing, fail closed with a clear error.
6. Make sure .env* is gitignored, and keep only names, never values, in .env.example.
7. For Cloud Run, pin a numeric version, for example NAME:2, not latest. To rotate: add a new version, then update the pin; that update deploys a new revision.
8. Run this project's build and tests, and confirm the app starts with no hard-coded values left.
9. Finish with a rotation checklist: every credential that ever touched git history is still in that history, so rotate or revoke it at its provider. Disable old versions before destroying them.Six questions
One thing to rememberIf you can answer these six, you have the model.
The short version
Code holds the name; Secret Manager holds the value as numbered versions. Pin your app to a number and let it read that one secret only.
To rotate: add a version, move the pin, check it works, disable the old version, then revoke and destroy it.
Fine print
- Audit logs. Admin Activity logs record changes and are always on. Reads (
AccessSecretVersion) go to Data Access logs; enable them in the project, folder or organization audit configuration. - Replication. Set at creation; it cannot change. User-managed replication can meet location rules. If access must stay in one region, use regional secrets.
- Destruction delay. Destroy is immediate by default. Set
--version-destroy-ttl(1 to 1,000 days) on the secret before you destroy; destroy then disables and schedules the version, and an admin can restore it until then. - Cost. Access operations have a monthly free allowance: 10,000 per billing account, per Google’s current pricing. It may change.
Every name and value here is a placeholder. The page makes no network calls.