Google Cloud Secret Manager Provider
The Google Cloud Secret Manager provider integrates with GCP for centralized secret management.
At a glance
| Provider | gcsm |
| URI | gcsm://PROJECT_ID |
| Access | Read and write; secret references are read-only |
| Best for | Workloads and teams on Google Cloud |
| Authentication | Google Application Default Credentials |
| Build feature | gcsm |
| Default storage | secretspec2--{project}--{profile}--{key} (0.20+) |
Quick start
# Set a secret$ secretspec set DATABASE_URL --provider gcsm://my-gcp-projectEnter value for DATABASE_URL: postgresql://localhost/mydb✓ Secret 'DATABASE_URL' saved to gcsm (profile: default)
# Run with secrets$ secretspec run --provider gcsm://my-gcp-project -- npm start
Terminal window
Setup
Prerequisites
- Google Cloud CLI (
gcloud) - GCP project with Secret Manager API enabled
- Build with
--features gcsm
Authentication
Google Cloud Secret Manager uses Application Default Credentials. For local development:
$ gcloud auth application-default login
Terminal window
In Google Cloud runtimes, Application Default Credentials use the attached service account automatically.
Configuration
URI format
gcsm://PROJECT_ID
PROJECT_ID: Your GCP project ID
URI examples
gcsm://my-gcp-projectProject configuration
[providers]google = "gcsm://my-gcp-project"
[profiles.production]DATABASE_URL = { description = "Database URL", providers = ["google"] }
secretspec.toml
Storage model
SecretSpec joins the project, profile, and key with validated --
boundaries. Distinct logical addresses therefore cannot collapse onto
one GCSM secret when a project or profile contains a single internal
hyphen. For example, project myapp, profile production, and key
DATABASE_URL map to:
secretspec2--myapp--production--DATABASE_URL
Each component may contain ASCII letters, digits, underscores, and
single internal hyphens. A component cannot start or end with - or
contain --, because those forms could overlap a boundary. The complete
GCSM id must fit the service’s 255-character limit.
Releases through 0.19 accepted project, profile, and key names the new
layout cannot represent, such as a project directory named my--app.
Reads of such an address keep serving the 0.19 secret and print a
warning, but writes fail until the name changes. Rename the offending
component and run secretspec set to store the value under the new id,
or address the secret with an explicit
ref, which is exempt
from the convention.
Reading legacy secrets
SecretSpec 0.20 reads the new id first. When that secret holds no value,
the read falls back to the 0.19 secretspec-{project}-{profile}-{key}
id and returns its latest value, printing one warning per run. A project
upgraded from 0.19 therefore keeps working with no migration step.
With secret-level IAM, an unbound new id can return PERMISSION_DENIED
instead of NOT_FOUND. SecretSpec still probes the legacy id in that
case and uses it when readable. If the legacy id supplies no value, the
original denial remains an error; failures other than the expected
permission denial from a legacy-id probe are also reported rather than
treated as a missing secret.
The fallback is a read. Nothing is created, copied, or deleted, so the
upgrade needs no new permissions: credentials holding only
roles/secretmanager.secretAccessor, the usual CI principal, keep
working unchanged.
Writes always use the new id. Running secretspec set for a secret is
what moves it, and afterwards reads stop consulting the legacy id. The
0.19 secret is left in place, so an older SecretSpec keeps reading the
value it knows and a rollback needs no recovery step.
Two consequences are worth planning for:
- A secret still served by the fallback depends on the 0.19 id continuing to exist. Delete legacy secrets only after the values that matter have been written under the new id.
- While a secret is served by the fallback, a 0.19 writer and a 0.20 writer update different ids. Point every writer at the same SecretSpec version, or set the secret with 0.20 to settle it on the new id.
Only the value is read across. Labels, rotation settings, secret-level IAM bindings, and other resource metadata belong to the legacy secret; reproduce any such configuration when you write the secret under its new id. If the legacy id had already received writes from colliding logical addresses, the provider cannot determine which historical version belonged to which address.
An explicit ref is a native address and is never renamed or migrated:
[profiles.production]DATABASE_URL = { description = "DB", ref = { item = "secretspec-myapp-production-DATABASE_URL" }, providers = ["google"]}Use existing secrets
A secret’s ref field
names an existing secret instead: item is the secret id, and the
optional version pins a version (defaults to latest; field is not
supported). References are read-only in this provider.
[profiles.production]DATABASE_URL = { description = "DB", ref = { item = "database-url" }, providers = ["gcsm://my-gcp-project"] }SIGNING_KEY = { description = "Key", ref = { item = "signing-key", version = "3" }, providers = ["gcsm://my-gcp-project"] }CI/CD
# Set credentials$ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"
# Run command$ secretspec run --provider gcsm://my-gcp-project -- deploy
Terminal window
Last updated Oct 08, 2026