Secret Manager: Storing API Keys and Credentials the Right Way on GCP
Every codebase, no matter how disciplined the team, has a moment where a database password or third-party API key ends up somewhere it shouldnโt โ a .env file accidentally committed to git, a hardcoded string in a config that gets copied between environments and never cleaned up, a Slack message with โhereโs the prod key, donโt share this.โ None of these are malicious; theyโre the natural result of not having a purpose-built place to put secrets thatโs easier to use correctly than to use wrong. Secret Manager is Googleโs answer: a centralized, versioned, access-controlled store for exactly this class of sensitive string โ API keys, database credentials, TLS certificates, service account keys, tokens.
The pitch isnโt complicated, but the value compounds over time in ways that arenโt obvious on day one: every secret access is logged, every secret has a version history, and access is controlled through the same IAM system you already use for everything else in GCP โ no separate secrets-management access model to learn or maintain.
Core Concepts
Secret (a named container, e.g. "prod-db-password") โ โผSecret Versions (each update creates a new immutable version) โ โโโ Version 1 (DISABLED) โโโ Version 2 (DISABLED) โโโ Version 3 (ENABLED โ the current active value)A secret is the logical name your application references โ prod-db-password, stripe-api-key. Updating a secretโs value doesnโt overwrite anything; it creates a new immutable version. This is deliberate: rotating a credential doesnโt destroy the ability to reference the previous value, which matters enormously during an incident where you need to quickly understand what changed or roll back to a previous version while investigating.
Creating and Accessing a Secret
# Create a secret and its first version in one stepecho -n "super-secret-db-password" | \ gcloud secrets create prod-db-password \ --data-file=- \ --replication-policy=automatic
# Add a new version (rotating the value)echo -n "new-rotated-password" | \ gcloud secrets versions add prod-db-password \ --data-file=-
# Access the latest versiongcloud secrets versions access latest --secret=prod-db-passwordFrom application code, the pattern is nearly identical across languages โ fetch the secret at startup or on demand, never bake it into a config file or Docker image:
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()name = "projects/my-project/secrets/prod-db-password/versions/latest"response = client.access_secret_version(request={"name": name})db_password = response.payload.data.decode("UTF-8")Referencing latest in application code is convenient but worth a second thought โ it means a secret rotation takes effect the moment the value changes, without a deploy, which is exactly the behavior you want for some secrets (an emergency credential rotation should propagate immediately) and exactly the behavior you donโt want for others (a change you want to test in staging before it silently affects production).
Pinning Versions vs Always Using Latest
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Reference pattern โ Behavior โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโคโ `latest` โ Always returns the newest enabled version โ โโ โ rotation is instant and implicit โโ Specific version # โ Always returns that exact version โ rotation โโ โ requires an explicit config/deploy change โโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโFor most application secrets, latest is the right default โ itโs what makes rotation actually low-friction rather than a deploy-coordination exercise. But for anything where an unexpected value change mid-request could cause hard-to-debug failures (a signing key used to validate incoming webhooks, for instance, where the sender and receiver must agree on exactly which key version is active), pinning to a specific version and rotating deliberately through a controlled deploy is the safer pattern.
Secrets as Environment Variables โ Without the Risk
A common integration pattern injects secrets directly into Cloud Run or GKE as environment variables, sourced from Secret Manager rather than a plaintext value in the deployment config:
gcloud run deploy my-service \ --image=gcr.io/my-project/my-service \ --set-secrets="DB_PASSWORD=prod-db-password:latest,API_KEY=stripe-key:latest"This gets you the ergonomics of a plain environment variable inside your running container โ no code changes needed to read os.environ["DB_PASSWORD"] โ while the actual secret value never touches your deployment YAML, your CI/CD logs, or your source control. The Cloud Run runtime resolves the reference and injects the real value only at container startup.
IAM: Who Can Access What
Secret Manager access control follows the same resource-scoped IAM model as the rest of GCP, and getting this right is most of what โusing Secret Manager securelyโ actually means in practice.
# Grant a specific service account access to a specific secret onlygcloud secrets add-iam-policy-binding prod-db-password \ --member="serviceAccount:api-service@my-project.iam.gserviceaccount.com" \ --role="roles/secretmanager.secretAccessor"roles/secretmanager.secretAccessor grants read access to secret values but not the ability to see or modify the secretโs metadata or add new versions โ a genuinely useful separation between โthis workload needs to read this credentialโ and โthis person manages the credentialโs lifecycle.โ Granting project-wide roles/secretmanager.admin to every service account that needs any secret is the single most common way teams undermine the isolation Secret Manager is designed to provide โ a compromised service account with broad secret access can read every credential in the project, not just the one it actually needs.
What Rotation Actually Looks Like End to End
Itโs worth walking through the full rotation sequence once, because the value of versioning is easy to state abstractly and much clearer when you see the actual sequence of events during a live rotation.
Notice that disabling and destroying are two separate, deliberately sequenced steps. Disabling a version blocks access but keeps it recoverable โ if the new version turns out to be wrong (a typo in a rotated password, for instance), you can re-enable the previous version immediately without regenerating anything. Destruction is the point of no return, and treating it as a separate, later step โ only after youโve confirmed the new version works in production โ is what makes rotation a safe, reversible operation rather than a one-way gamble.
Real-World Use Case: Rotating a Compromised API Key Under Pressure
This is the scenario that makes the investment in Secret Manager obviously worth it in hindsight: a third-party API key is accidentally exposed (in a log line, a support ticket, a public repo). Without centralized secret management, remediation means finding every place that key is hardcoded or copied across config files, environments, and possibly other engineersโ local setups โ a slow, error-prone scramble under real time pressure. With Secret Manager, remediation is: rotate the key with the third-party provider, add the new value as a new secret version, and every service referencing latest picks up the new value automatically on next access, no code deploy required. The difference between these two remediation timelines, during an actual incident, is the entire argument for adopting this pattern before you need it.
Best Practices
- One secret per credential, not one secret holding a JSON blob of everything. Granular secrets let you scope IAM access precisely โ a service that only needs the database password shouldnโt also be able to read the Stripe key just because they were bundled together.
- Enable automatic replication unless you have a specific regional data-residency requirement, which needs user-managed replication instead โ automatic replication handles multi-region durability without you managing it.
- Set up secret rotation reminders or automation, especially for credentials tied to third-party systems where Secret Manager itself canโt force the upstream provider to rotate โ a stale, never-rotated secret undermines a lot of the security value of the system around it.
- Audit access logs periodically, not just when investigating an incident โ Cloud Audit Logs captures every secret access, and reviewing whoโs actually accessing production secrets is a genuinely useful, underused check.
- Never log secret values, even at debug level. It sounds obvious, but a debug log line that accidentally prints a fetched secret is one of the most common ways a properly-stored credential still ends up leaked โ through the logging pipeline rather than through Secret Manager itself.
Common Mistakes
Treating Secret Manager as a place to โset and forgetโ rather than actively rotate. Storing a credential securely doesnโt make it immune to leaking eventually โ regular rotation limits the blast radius when it does.
Granting broad secretmanager.admin access instead of scoped secretAccessor per secret. This defeats much of the isolation value Secret Manager provides over a shared config file.
Not disabling old versions after rotation. Old, disabled-but-not-destroyed versions remain recoverable (useful for rollback), but leaving every historical version enabled indefinitely means a compromised credential from months ago might still technically be usable if an old version is ever mistakenly referenced.
Frequently Asked Questions
Is Secret Manager the same as Cloud KMS? No โ KMS manages cryptographic keys; Secret Manager stores actual secret values (which, under the hood, are encrypted using KMS-managed keys). Use Secret Manager for credentials and API keys, KMS for encrypting your own data directly.
Does Secret Manager have a size limit? Yes, secret payloads are capped (64 KiB per version), which is more than sufficient for credentials and keys but not intended for storing larger files or certificates bundles beyond that size โ those typically belong in Cloud Storage with restricted access instead.
Can I get notified when a secret is accessed or changed? Yes, via Cloud Audit Logs and Pub/Sub notifications configured on secret events, which is commonly wired into alerting for high-sensitivity secrets.
What happens if I destroy a secret version thatโs still referenced by a running service? Access attempts against a destroyed version fail immediately and irreversibly โ destruction, unlike disabling, cannot be undone. Disable a version first and confirm nothing depends on it before destroying.
Can Secret Manager automatically rotate credentials for me? Secret Manager itself only manages versions and can notify you on a schedule that rotation is due โ it doesnโt generate new credential values for arbitrary third-party systems on its own. For GCP-native credentials, pairing it with a Cloud Function triggered on a schedule that generates a new value and calls the versions.add API is a common pattern for genuine automated rotation.
Should secrets shared across multiple environments (dev, staging, prod) live in one project or be separated? Separate projects per environment, each with its own Secret Manager secrets, is the safer default โ it means a developer with access to staging secrets never has an implicit path to production credentials, which a shared project with only IAM-level separation canโt guarantee as cleanly.
Summary
Secret Managerโs value isnโt a new capability so much as removing every excuse for the bad habits that lead to leaked credentials โ hardcoded values, shared plaintext files, no audit trail. The pattern that actually delivers on that promise is disciplined: one secret per credential, IAM access scoped to exactly the workload that needs it, and rotation treated as a routine operational task rather than something that only happens reactively after an incident. Once that discipline is in place, the payoff shows up exactly when you need it most โ during a real credential rotation under time pressure, where the difference between minutes and hours of remediation work is whether you built this correctly beforehand, long before the incident that actually tests it.