Environment Variables
ScramDB reads a small set of environment variables directly. Anything not listed here belongs in the config file instead.
Variablesβ
| Variable | Default | Description |
|---|---|---|
SCRAMDB_LICENSE_KEY | unset (Community edition) | Enterprise or Trial license token. Takes precedence over license_key under [general] when both are set. |
SCRAMDB_INITIAL_PASSWORD_FILE | unset (no seeding) | Recommended source for the bootstrap superuser's first password: path to a file whose first line is the password, read once at first boot. See Seeding the superuser password. |
SCRAMDB_INITIAL_PASSWORD | unset (no seeding) | The bootstrap superuser's first password as a literal value, read once at first boot. Weaker than _FILE: visible in kubectl describe pod and /proc/<pid>/environ. See Seeding the superuser password. |
SCRAMDB_RANDOM_INITIAL_PASSWORD | unset (no seeding) | Set to exactly yes to generate a strong random password for the bootstrap superuser at first boot and log it once. See Seeding the superuser password. |
SCRAMDB_LOG | info | Log level filter: off, error, warn, info, debug, or trace (case-insensitive). |
SCRAMDB_LOG_FORMAT | text | Log output format. Set to json for structured JSON logs; any other value stays plain text. |
SCRAMDB_GPU_FORCE | unset (not forced) | Force GPU pipeline eligibility on regardless of the usual size heuristics. Set to 1 or true. |
SCRAMDB_JIT_PERFMAP | unset (not emitted) | Emit a perf-compatible jitdump file for JIT-compiled query frames, so perf inject --jit can resolve them in a flamegraph. |
SCRAMDB_MAX_CORES | unset (auto-detected) | Caps how many cores every worker pool claims, not just the main query pool. Picks N topology-compact cores and overrides both cpuset and cgroup-quota detection; explicit always beats inferred. Running several ScramDB instances on one host, each set to a different value, rotates them onto separate core windows. See Parallelism. |
Seeding the superuser passwordβ
ScramDB is secure by default, and the default is not a broken one: a fresh node's bootstrap superuser (scramdb) starts with no credential at all. It authenticates with no password from 127.0.0.1 only (the built-in hba_file default trusts localhost and asks everywhere else for a password), and it is unreachable from anywhere else until you either set a password on it from that trusted local connection, or seed one in before first boot.
That default runs into exactly one real problem: a Helm or kustomize install has no localhost to run psql from except kubectl exec, so the very first connection to a chart-driven cluster needs a credential seeded in from the outside. SCRAMDB_INITIAL_PASSWORD_FILE, SCRAMDB_INITIAL_PASSWORD, and SCRAMDB_RANDOM_INITIAL_PASSWORD exist for exactly that first connection, and nothing else.
The three sources, rankedβ
Exactly one may be set at a time. In order of preference:
-
SCRAMDB_INITIAL_PASSWORD_FILE(recommended). A path to a file whose first line is the password: a Kubernetes Secret or Docker secret mounted into the container. The value itself is never present in the pod spec or in/proc/<pid>/environ, only the file path is. -
SCRAMDB_INITIAL_PASSWORD. The literal password value. Every chart expects it, and it's simpler to wire up, but weaker: the value is visible inkubectl describe podand in/proc/<pid>/environto anything that can read the container's process table. -
SCRAMDB_RANDOM_INITIAL_PASSWORD. Set to exactlyyesto generate a strong random password (24 alphanumeric characters, about 143 bits of entropy) at first boot and log it exactly once, the same convention MySQL's--initializeuses. Any other value, includingtrue,1, orYes, is rejected; the comparison is case-sensitive. The one time it's shown:generated a random initial superuser password - THIS IS THE ONLY TIME IT WILL BE SHOWN: <password>
Setting more than one at once fails startup loud, naming every source that was actually set, rather than resolving the conflict by precedence. For example, setting both SCRAMDB_INITIAL_PASSWORD and SCRAMDB_RANDOM_INITIAL_PASSWORD:
more than one initial superuser password source is set (SCRAMDB_INITIAL_PASSWORD and SCRAMDB_RANDOM_INITIAL_PASSWORD); unset all but one - ambiguity about which credential is live is refused, never resolved by precedence
Setting a variable to an empty string still counts as "set" for this check: a Helm template that always renders SCRAMDB_INITIAL_PASSWORD="" when a value is absent will collide with a real SCRAMDB_INITIAL_PASSWORD_FILE on the same pod, not silently defer to it.
Parsing rulesβ
-
SCRAMDB_INITIAL_PASSWORD_FILEreads the whole file and takes its first line. Only the line terminator itself (\n, or\r\n) is stripped; no other whitespace is trimmed, so a password with a meaningful leading or trailing space round-trips exactly. Anything after the first line is ignored, the same contract PostgreSQL's owninitdb --pwfileuses. -
SCRAMDB_INITIAL_PASSWORDis used exactly as the environment holds it, with no trimming. -
Either way, a password that resolves to empty, or to whitespace only, is refused rather than silently treated as "no password requested":
the initial superuser password from SCRAMDB_INITIAL_PASSWORD is empty or whitespace-only -
A missing or unreadable
SCRAMDB_INITIAL_PASSWORD_FILEpath fails startup loud, naming the path, for example:SCRAMDB_INITIAL_PASSWORD_FILE "/etc/scramdb-secrets/initial-password" could not be read: No such file or directory (os error 2) -
A password file that's readable beyond its own owner (group- or world-readable) only warns in the startup log; it does not block startup. Restrict it anyway:
chmod 600 <path>. -
An unrecognized
SCRAMDB_RANDOM_INITIAL_PASSWORDvalue fails startup loud too:SCRAMDB_RANDOM_INITIAL_PASSWORD="true" is not recognized; set it to "yes" or unset it
Any failure above is fatal at startup, never a degraded boot: ScramDB prints FATAL: initial superuser password seeding failed: <reason> to stderr and exits immediately.
Idempotence: every later restart is a no-opβ
This is the guarantee that makes seeding safe to leave wired into a chart permanently. ScramDB checks whether the superuser already has a credential before it looks at any of the three variables. If it does, seeding logs a one-line no-op and returns, without reading any of the three variables and without re-running the "exactly one may be set" check either. That ordering is what the guarantee rests on:
- A redeployed pod, a rotated Secret, or a leftover environment variable from an old Helm values file can never reset an operator-set password or lock anyone out.
- Leaving two or three of the variables set after the cluster is already bootstrapped does not crash-loop a healthy pod: the conflict check never runs once a credential exists.
- Only a superuser with no credential at all (a brand-new cluster, or one where the password was explicitly cleared with
ALTER ROLE scramdb PASSWORD NULL) is eligible for seeding.
The seeded credential is stored exactly the way ALTER ROLE ... PASSWORD stores one: a SCRAM-SHA-256 verifier (PBKDF2-HMAC-SHA256, 4096 iterations). The plaintext password is never written to the role catalog, seeded or not.
Kubernetesβ
Create the Secret once, before the first apply:
kubectl create secret generic scramdb-initial-password \
--from-literal=initial-password="$(openssl rand -base64 24)"
Mount it read-only and point SCRAMDB_INITIAL_PASSWORD_FILE at the mounted path:
containers:
- name: scramdb
env:
- name: SCRAMDB_INITIAL_PASSWORD_FILE
value: /etc/scramdb-secrets/initial-password
volumeMounts:
- name: initial-password
mountPath: /etc/scramdb-secrets
readOnly: true
volumes:
- name: initial-password
secret:
secretName: scramdb-initial-password
defaultMode: 0400
defaultMode: 0400 mounts the file owner-readable only, matching the permission warning above. The ScramDB repository's k8s/base/statefulset.yaml carries this exact recipe, commented out by default since the base manifest runs --pg-no-auth for its local kind demo cluster.
Docker and bare metalβ
The same variables work outside Kubernetes too: a plain docker run, or the scramdb binary directly on bare metal, anywhere you want a deterministic bootstrap credential without a manual interactive step.
openssl rand -base64 24 > ./initial-password
chmod 600 ./initial-password
docker run -d \
--name scramdb \
-p 5432:5432 \
-p 9090:9090 \
-v scramdb-data:/var/lib/scramdb \
-v $(pwd)/initial-password:/run/secrets/scramdb-initial-password:ro \
-e SCRAMDB_INITIAL_PASSWORD_FILE=/run/secrets/scramdb-initial-password \
scramdb/scramdb:latest \
--pg-address 0.0.0.0:5432
On bare metal, the same variable points at a local file, no container involved:
openssl rand -base64 24 > /etc/scramdb/initial-password
chmod 600 /etc/scramdb/initial-password
SCRAMDB_INITIAL_PASSWORD_FILE=/etc/scramdb/initial-password \
scramdb -c /etc/scramdb/config.toml
Either way, the startup log names the source once:
seeded initial superuser "scramdb" password from SCRAMDB_INITIAL_PASSWORD_FILE
and the same credential connects from then on:
psql "host=127.0.0.1 port=5432 user=scramdb dbname=scramdb"
Container-only variablesβ
The official Docker image's docker/entrypoint.sh also reads NODE_NAME, ADVERTISE_ADDR, SEEDS, and CONFIG_PATH to render a config file from a template before it starts scramdb. The scramdb binary itself never reads these four; they only matter if you rely on the image's built-in templating instead of mounting your own config file. See Clustering and Docker.