GOOGLE_GENERATIVE_AI_API_KEY is checked at module import. The process
throws and exits without it, even for endpoints that never call a model. There
is no way to run Memory Soda without a Gemini key.
DATABASE_URL accepts the standard libpq form, including ?sslmode=require:
true is right for a single instance. With several replicas booting together
they race, safely, because Drizzle takes a lock, but slowly. For a fleet, set it
to false and migrate as a deploy step. See Migrations.
Only used on an empty database, when the first admin user is seeded.
Leave it unset in production. A random password is generated and printed once
safer than a value that ends up in your deployment config and shell history.
Login: admin / kR7v-2mQxPd1
(generated, set ADMIN_PASSWORD to choose)
Neither the generated password nor the API key is recoverable after the log
scrolls.
All optional. Defaults shown; omit them and nothing changes.
Variable
Default
Description
GEMINI_MODEL
gemini-2.5-flash
Model for summarisation, extraction, contradiction judging and synthesis.
GEMINI_TIMEOUT_MS
30000
Timeout for interactive text calls.
GEMINI_STRUCTURED_TIMEOUT_MS
90000
Timeout for schema-constrained calls (extraction, judging). Higher because these run in background jobs and tolerate thinking-mode tail latency.
GEMINI_EMBED_MODEL
models/gemini-embedding-001
Embedding model. Include the models/ prefix.
GEMINI_API_BASE_URL
https://generativelanguage.googleapis.com/v1beta
Base for the REST embedding endpoint. Point it at a proxy or gateway.
GEMINI_EMBED_DIM
768
Embedding dimensionality. See the warning below.
The embedding URL is derived as ${GEMINI_API_BASE_URL}/${GEMINI_EMBED_MODEL}
rather than configured separately, so changing the model cannot leave the URL
pointing at the previous one.
A malformed numeric value logs a warning and falls back to the default rather
than failing silently:
[gemini] GEMINI_TIMEOUT_MS="soon" is not a positive number, using 30000
GEMINI_EMBED_DIM is not really a runtime setting. The facts, entities
and episodes tables declare vector(768) columns, so any other value is
rejected on insert. Changing it means a migration and re-embedding every
stored vector. The API warns at startup if the two disagree:
[gemini] GEMINI_EMBED_DIM=1536 does not match the vector(768) columns in the
database, embedding writes will fail until the schema is migrated.
Switching GEMINI_EMBED_MODEL has the same consequence whenever the new model
emits a different number of dimensions, and existing vectors were produced by
the old model, mixing them in one index gives meaningless similarities even
when the dimensions happen to match.
This is the browser’s view, not the server’s. http://api:3004 works inside a
Docker network and is useless to a user’s browser.
Changing it requires a rebuild, it is not read at runtime.
DASHBOARD_PORT is the exception: it is a dev-server setting, not a bundled
value. Move the dashboard off 3000 and CORS_ORIGIN on the API has to follow,
or the browser’s requests are rejected. npm create memory-soda@latest writes
both from one answer.
Every variable is read in exactly one place, apps/api/src/config.ts, parsed
and validated once at import. Nothing else in the API touches process.env.
That means a misconfigured deployment fails on boot with every problem listed
at once, rather than one variable at a time:
Error: Invalid environment configuration:
DATABASE_URL: DATABASE_URL is required
GOOGLE_GENERATIVE_AI_API_KEY: GOOGLE_GENERATIVE_AI_API_KEY is required
See .env.example for the full list of supported variables.
A malformed value is an error too, not a silent fallback, PORT=not-a-port
stops the boot instead of quietly reverting to 3004.
The API does not load .env itself; that comes from the Nx dev server during
npm run dev. In production, supply variables through your process manager,
container runtime or secret store.
A .env file sitting next to a production build is ignored.
A .env written by npm create memory-soda@latest sets only the values the
installer asks for, DATABASE_URL, the Gemini key, and the admin login.
Everything else stays on the defaults in config.ts rather than being pinned to
a copy of them, so the file does not go stale when a default changes.
node-e"for (const k of ['DATABASE_URL','GOOGLE_GENERATIVE_AI_API_KEY']) if (!process.env[k]) { console.error('missing', k); process.exit(1) } console.log('ok')"
# database reachable, extension present?
psql"$DATABASE_URL"-c"SELECT extname FROM pg_extension WHERE extname = 'vector';"