Skip to main content
Version: 5.x

Secrets Providers

There are lots of "secrets" needed to configure Redwood properly. These include passwords, API keys, and other sensitive information. Redwood's config system integrates a secrets system so you can store these secrets somewhere else, while still committing non-sensitive references to version control.

warning

We no longer support HashiCorp's managed HCP Vault Secrets SaaS since the service was sunset on June 30, 2025. Setting the provider to hcp-vault throws an error.

How to specify which provider is used?​

The config variable secrets.provider takes a string enum, which can be none, local, vault, aws-secrets-manager, azure-key-vault, or gcp-secret-manager. The default (in config/node/default/secrets.yaml) is none.

You can override this variable in your own config environment, for example in config/node/your-game/secrets.yaml:

provider: "local"

See the secrets config reference for every variable.

Caching​

Secrets can be requested several times, especially during backend initialization. For performance (and sometimes cost) reasons, the secrets system caches each secret value in memory for a certain duration before fetching it again.

You can change the cache duration with secrets.cache-duration-seconds, which defaults to 1800 (30 minutes). There's no hot reload; a rotated secret is picked up the next time its cached value expires.

How to leverage the secrets system​

Referencing a secret​

Secret references use the syntax secret|<secret_name>. If the value of a field that supports secrets doesn't have the secret| prefix, it's used as a plaintext value. For example:

docker:
registry:
auth:
password: "secret|do_registry_password"

You can also embed one or more secrets inside a larger string by wrapping each reference in parentheses, (secret|<secret_name>):

some-connection-string: "postgres://redwood:(secret|db_password)@db.example.com:5432/redwood"

If a config value references another value with ${...} and that value is a secret reference, the reference is carried along, so it's resolved as a secret too. For example, the default director.persistence.database.deployment-access.password is ${director.persistence.database.runtime-access.password}, so setting only the runtime-access password to a secret reference covers both.

note

All secrets are processed as strings. You'll need to do further parsing if you need them to be a different type.

Which variables support secrets?​

To keep things fast and reduce API calls, not every variable supports secrets; secret references are only resolved where the backend reads the value with the secrets system. In the default config files (config/node/default/**/*.yaml), these variables are marked with the comment # This can be a secret in the secrets provider. As of 5.0, that includes:

AreaVariables
Adminadmin.default-user.password
Authauth.admin.secret, auth.jwt.secret, auth.steam.api-key, auth.discord.client.secret, auth.twitch.client.secret
Directordirector.persistence.database.runtime-access.password, director.persistence.database.deployment-access.password, director.gateway.secret
Realmsrealm.instances.<key>.auth.secret (but see this known issue with yarn deploy), ...persistence.database.runtime-access.password, ...persistence.database.deployment-access.password, ...persistence.blobs.s3.connection.credentials.access-key-id, ...secret-access-key, ...game-servers.edgegap.credentials.api-token
Redispersistence.redis.password
Deploymentdocker.registry.auth.password, deployment.dns.cloudflare.credentials.token, deployment.pulumi.access-token, deployment.pulumi.encryption.passphrase, deployment.kubernetes.instances.<id>.kubeconfig, the in-cluster PostgreSQL passwords, and the SeaweedFS keys
Teststests.artillery.key
warning

The email provider API keys (email.sendgrid.api-key and email.zeptomail.api.key) don't support secrets in 5.0; they're used as plaintext.

Adding secrets support to a variable​

If you have source access, you can add secrets support for a variable by changing the corresponding TypeScript code that reads it:

- config.get<string>("docker.registry.auth.password"); // note that this is a synchronous function
+ await config.getWithSecrets<string>("docker.registry.auth.password"); // note this is an asynchronous function

You can fetch secrets for an entire object too, and all the child fields with the secret| prefix will be resolved:

interface Auth {
notSecret: string;
key: string;
secret: string;
}

const bothKeyAndSecretCanBeSecrets = await config.getWithSecrets<Auth>("my-auth-key");

Secrets in Kubernetes​

When you deploy to Kubernetes, the deployment scripts make the provider's credentials available to the backend pods for you:

ProviderWhat the deployment does
localCopies your secrets file into a Kubernetes Secret and mounts it in each pod
vault, aws-secrets-manager, azure-key-vaultCopies the provider's authentication environment variables (from the machine running the deployment) into the pods
gcp-secret-managerMounts the credentials file in each pod

Checking for Placeholder Secrets​

Production-like environments (anything inheriting from staging, which includes production) have environment.check-for-default-secrets.enabled set to true. When it's on, yarn deploy refuses to deploy while any of the listed secret variables still resolves to the placeholder value shipped in config/node/default (e.g. the admin admin password or the all-zeros realm secret). You can run the same check yourself with:

yarn check-secrets <your-config-env>

See the environment config reference for the list of checked keys.