Use a secrets manager
If your organisation already keeps passwords and keys in a secrets manager, the server can fetch them from it when it starts, so that they never sit in a file on the server. This page is for whoever runs a Node or Docker site with one of the four managers the site knows: HashiCorp Vault, AWS Secrets Manager, Doppler and Infisical.
How it works
Set SECRETS_PROVIDER and the few variables that say where the manager is and how to log in to it. Those few have to be ordinary environment variables: they are what the server uses to reach the manager.
SECRETS_PROVIDER | Where secrets come from |
|---|---|
env, or unset | Environment variables only (and NAME_FILE files). The default. |
vault | HashiCorp Vault |
aws-secrets-manager | AWS Secrets Manager |
doppler | Doppler |
infisical | Infisical |
Capitals in the value do not matter.
What happens at start:
-
The server reads any
NAME_FILEsettings. -
It asks the manager for the secrets, once.
-
It puts each one into its environment under the secret's name, so store them in the manager under the same names as the settings:
ADMIN_PASSWORD,RESEND_API_KEY,DATABASE_URL. -
It reads
NAME_FILEsettings again, in case the manager supplied one. -
It logs how many values it took:
[secrets] 6 value(s) loaded from vault
The rules:
- A variable that is already set wins. The manager's value is used only where the environment has none, or has an empty one. That makes a local override easy, and it means a stale value left in a
.envfile hides the manager's. - Only upper-case names are applied. A name must start with a capital letter and contain only capitals, digits and underscores. A secret called
adminPasswordoradmin-passwordis skipped silently. The count in the log line tells you how many were taken. - Secrets are fetched once. After changing one in the manager, restart the site.
- The server refuses to start if the manager cannot be reached or refuses the request. A site running without its admin login and email settings would be worse than one that is down. The error follows
[server] failed to start:in the log. PORTandDATA_DIRcannot come from the manager. The launcher reads them before any secret is fetched.
The helper scripts (config:check, admin:add, db:migrate) fetch secrets the same way, so they need the same variables and the same network access.
HashiCorp Vault
| Variable | What it is | Default |
|---|---|---|
VAULT_ADDR | The address of the Vault server, such as https://vault.example.org:8200. | none, required |
VAULT_TOKEN | A token allowed to read the secret. | none, required |
VAULT_SECRET_PATH | The path of the secret, as it appears after /v1/ in Vault's HTTP API. | none, required |
VAULT_NAMESPACE | The namespace, for Vault Enterprise and HCP Vault. | none |
Every key in that one secret is fetched. Both versions of the key-value engine work, but the path differs:
- KV version 2 has
datain the path: a secret written withvault kv put secret/choir …is atsecret/data/choir. - KV version 1 has no
data:secret/choir.
SECRETS_PROVIDER=vault
VAULT_ADDR=https://vault.example.org:8200
VAULT_TOKEN=hvs.example
VAULT_SECRET_PATH=secret/data/choir
The token is used as given. The server does not log in with AppRole or another method, and does not renew the token, so give it one that is still valid whenever the site restarts.
AWS Secrets Manager
| Variable | What it is | Default |
|---|---|---|
AWS_SECRETS_MANAGER_SECRET_ID | The name or ARN of the secret. | none, required |
AWS_REGION | The region the secret is in. | none, required |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY | Credentials allowed secretsmanager:GetSecretValue on that secret. | none, required |
AWS_SESSION_TOKEN | The session token, when the credentials are temporary. | none |
The secret must be one secret whose value is a JSON object, which is what the AWS console makes when you choose "Other type of secret" and enter key and value pairs:
{
"ADMIN_PASSWORD": "a-long-random-password",
"RESEND_API_KEY": "re_your_key"
}
SECRETS_PROVIDER=aws-secrets-manager
AWS_SECRETS_MANAGER_SECRET_ID=choir/production
AWS_REGION=ca-central-1
AWS_ACCESS_KEY_ID=AKIAEXAMPLE
AWS_SECRET_ACCESS_KEY=example
Two things to know:
- The credentials must be given as these variables. The server does not pick them up from an instance role, a container role or a profile in
~/.aws. AWS_REGION,AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYare the same variables Amazon SES uses for email. If you send through SES, the same credentials and region serve both unless you give SES its own with theTRANSACTIONAL_andANNOUNCEMENT_forms.
Doppler
| Variable | What it is | Default |
|---|---|---|
DOPPLER_TOKEN | A service token for one config. | none, required |
A service token belongs to one project and one config, so the token alone says which secrets to fetch. Every secret in that config is fetched.
SECRETS_PROVIDER=doppler
DOPPLER_TOKEN=dp.st.example
Infisical
| Variable | What it is | Default |
|---|---|---|
INFISICAL_TOKEN | A token allowed to read the secrets. | none, required |
INFISICAL_PROJECT_ID | The project's id. | none, required |
INFISICAL_ENVIRONMENT | The environment's short name, such as prod. | none, required |
INFISICAL_SECRET_PATH | The folder to read. | / |
INFISICAL_URL | The address of your own Infisical server. | https://app.infisical.com |
Every secret in that folder is fetched. Sub-folders are not.
SECRETS_PROVIDER=infisical
INFISICAL_TOKEN=st.example
INFISICAL_PROJECT_ID=00000000-0000-0000-0000-000000000000
INFISICAL_ENVIRONMENT=prod
Check it
node --env-file=.env scripts/check-config.js
The check fetches from the manager as the server would. Look for the [secrets] … value(s) loaded from … line above the JSON, and for (set) beside the secrets you expect. The last line of the JSON, "secrets", names the provider in use.
If something goes wrong
| What you see | Cause |
|---|---|
Unknown SECRETS_PROVIDER "…". Use env, vault, aws-secrets-manager, doppler or infisical. | A misspelt value. |
VAULT_ADDR, VAULT_TOKEN and VAULT_SECRET_PATH are required, and the like for the other three | One of that manager's required variables is missing. |
A status code and the manager's answer, such as 403 Forbidden: … or 404 Not Found: … | The manager refused: a wrong or expired token, a wrong path, or a token without permission to read. For Vault, a 404 is often a KV version 2 path without data in it. |
fetch failed | The manager could not be reached at all: a wrong address, a firewall, or a certificate Node does not trust. |
[secrets] 0 value(s) loaded from … | The manager answered, but nothing was taken: every name was already set in the environment, or none was in upper case. |
Other managers
For 1Password, Bitwarden Secrets Manager, Azure Key Vault, Google Secret Manager and others, use the tool's own command to start the site with the secrets in its environment, or have it write them to files: Keep secrets in files.