Skip to content

Configuration ​

Castor has two kinds of configuration:

  • Environment variables: what the server needs before it can reach the database (database URL, SECRET_KEY, ports …), validated with Zod in apps/api/src/config.ts and injected by docker-compose.yml in Docker deployments. See "Backend (API)" below
  • System settings: mail, file storage, upload limits, the AI model, the site URL, the sign-in lockout and security switches such as two-step verification, edited after signing in under System → Configuration → System settings and applied within seconds, without a restart. Environment variables can pin these too, see Configuration in System settings

Config files ​

On startup, the backend loads .env.<NODE_ENV> based on NODE_ENV:

  1. apps/api/.env.<NODE_ENV>
  2. .env.<NODE_ENV> in the repo root

When both files set the same variable, the value in apps/api/ wins. Environment variables that are already set (for example by the shell or by compose) are never overridden by a file.

FilePurposeCommitted
apps/api/.env.exampleLocal development exampleYes
.env.production.example (repo root)Annotated example of every variable (reference for Docker / production)Yes
apps/api/.env.developmentLocal development configNo (gitignored)
.env.production (repo root)Docker deployment config, generated by setup.shNo (gitignored)

Never commit real secrets

.env.development and .env.production are gitignored. Don't put a real SECRET_KEY, database password or API key into an example file or commit it to the repo.

Backend (API) ​

Runtime and database ​

VariablePurposeDefault
NODE_ENVRuntime environment: development / test / production; any other value is treated as developmentdevelopment
PORTListening port5001 in development, 5002 in test, 5000 in production
APP_NAMEProduct name the server shows: the issuer in authenticator apps, the default mail sender and the test mail's subject, the AI assistant's introduction, the startup log. The web app's name is APP_NAME in apps/web/src/lib/brand.ts (see Starting a project)Castor
DEV_DATABASE_URLDevelopment database connectionpostgresql://localhost/castor_kit
TEST_DATABASE_URLTest database connection; the tests read it from the shell or apps/api/.env.testpostgresql://localhost/castor_kit_test
DATABASE_URLProduction database connectionpostgresql://localhost/castor_kit
MIGRATIONS_DIRMigrations directoryFound automatically by searching upward for a drizzle/ directory

Security and sessions ​

VariablePurposeDefault
SECRET_KEYSession encryption key; the cookie key is derived from it via HKDFBuilt-in insecure default in development / test; required in production
ADMIN_PASSWORDInitial password of the admin account, used only when the account doesn't exist (pnpm seed:rbac -- --incremental --reset-admin-password, or in Docker node dist/reset-admin-password.js, applies it to an existing account)admin123 in development / test; required in production
SESSION_TTL_HOURSInitial session lifetime (hours); can be changed later in System settings8
COOKIE_SECUREThe cookie's Secure flag: true / false forces it; leave empty to decide from the request protocol (set only over HTTPS)Empty (auto)
CORS_ORIGINSAllowed cross-origin origins, comma-separated; also used as the Origin allowlist for the WebSocket handshakeEmpty
RATE_LIMIT_ENABLEDPer-IP rate limits; the limits themselves are set in System settings, see Account security & settingstrue
SETTINGS_ALLOW_PRIVATE_NETWORKWhether the SMTP server, S3 endpoint and AI API URL in System settings, and webhook endpoints, may point at internal networks (127.0.0.1, 10.x, 192.168.x …); reserved addresses such as cloud metadata are never allowed. Addresses pinned by environment variables aren't restrictedtrue in development / test, false in production
BODY_LIMITMaximum request body size (bytes); larger requests get 41316777216 (16MB)

Paths ​

VariablePurposeDefault
WEB_DIST_DIRFrontend build directory; the backend serves static files and the SPA from hereapps/web/dist
DATA_DIRRuntime data directory; the local driver stores uploads in its uploads/files/ by defaultapps/api/data

File storage directory and mail development mode ​

VariablePurposeDefault
STORAGE_LOCAL_DIRDirectory of the "local disk" storage<DATA_DIR>/uploads/files
MAIL_DRIVEREmpty = send through the SMTP server in System settings; log prints mails to the backend log instead of sending (local development); none never sendsEmpty

Local-disk storage needs a persistent disk: Docker Compose mounts DATA_DIR as a volume; on platforms like Render that wipe the disk on redeploy, switch to S3-compatible storage (e.g. Cloudflare R2) in System settings, or with the STORAGE_DRIVER / S3_* variables where the page is read-only (demo mode). Files that nothing references are removed by the task scheduler 24 hours after upload, so they aren't cleaned up while no scheduler runs (ENABLE_TASK_SCHEDULER=false, or RUN_SCHEDULER_IN_WEB=false without a separate worker process).

Public demo ​

VariablePurposeDefault
DEMO_MODEPublic demo mode: the login page shows the demo account with one-click sign-in; every write except sign-in, the component gallery, file uploads and marking notifications read returns 403 (system management is read-only and passwords can't be changed); login lockout counts per IP only; sample data is restored periodicallyfalse
DEMO_RESET_HOURSHow often the demo data is restored (hours). Checked at startup and then hourly; restores when the last restore is older than this. Run pnpm demo:reset to restore right away24
DEMO_AI_HOURLY_PER_IPAI calls (AI chat, AI SQL generation, the AI assistant) allowed per IP per hour in demo mode; over the limit returns 429. Signed-out requests don't count20
DEMO_AI_DAILYAI calls allowed per day for the whole site in demo mode; once used up, 429 for the rest of the day300
DEMO_AI_MAX_INPUT_CHARSMax size of one AI request in demo mode (characters; for AI chat only the message text counts); larger requests return 400. Demo mode also caps the length of the model's reply4000

The demo data lives in apps/api/src/demo/fixtures.ts and the restore logic in apps/api/src/demo/reset.ts. A restore only touches the component gallery, announcements, dictionaries, scheduled tasks, notifications and logs — never accounts, roles or menus.

Scheduled tasks ​

VariablePurposeDefault
ENABLE_TASK_SCHEDULERWhether the task scheduler is enabledtrue
RUN_SCHEDULER_IN_WEBWhether to run the scheduler inside the web process; when false, you need to run the worker process separatelyfalse
TASK_SCHEDULER_INTERVAL_SECONDSScheduler scan interval (seconds)20
TASK_SCHEDULER_LEASE_SECONDSTask lease duration (seconds), used to prevent duplicate runs and reclaim stuck tasks1800

Boolean values 1, true, yes and on (case-insensitive) count as true.

AI Data Query ​

The AI model (API URL, key, model name) is configured in System settings, see below. This section only holds the read-only database connection used by AI Data Query:

VariablePurposeDefault
AI_SQL_DATABASE_URLRead-only connection used by AI Data Query; should point at a non-superuser read-only accountFalls back to the main database connection in development / test (still forced read-only). In production, if unset but POSTGRES_RO_PASSWORD is set, it is derived from DATABASE_URL (switched to the castor_kit_ro account); otherwise startup fails
AI_SQL_STATEMENT_TIMEOUT_MSPer-statement timeout for AI Data Query (milliseconds)5000
POSTGRES_RO_PASSWORDPassword of the read-only account castor_kit_ro; setup-once / init-ro-role use it to create the account, and skip that step if it's not setEmpty

Apifox (used only by pnpm openapi:apifox) ​

VariablePurposeDefault
APIFOX_PROJECT_IDApifox project IDEmpty
APIFOX_ACCESS_TOKENApifox access tokenEmpty
APIFOX_API_VERSIONApifox API version2024-03-28

Required in production ​

With NODE_ENV=production, the server refuses to start if any of these is missing:

  • SECRET_KEY
  • ADMIN_PASSWORD
  • AI_SQL_DATABASE_URL, or POSTGRES_RO_PASSWORD (the read-only connection is then derived from it and DATABASE_URL)

When deploying with docker-compose.yml, compose builds AI_SQL_DATABASE_URL for you, so you don't need to set it by hand.

Configuration in System settings ​

These are edited on the System settings page (viewing needs system_settings, saving system_settings_edit) and apply within seconds. The Mail, File storage and AI tabs have test buttons that try unsaved values.

  • Passwords, the S3 secret key and the API key are stored encrypted with a key derived from SECRET_KEY; the page only shows that they are set. After changing SECRET_KEY, enter them again
  • When the matching environment variable is set (and not empty), it wins: the setting becomes read-only on the page and names the variable. Useful for deployments managed entirely through environment variables; leave them unset to manage settings on the page
  • To pin a setting in Docker, add the variable to app.environment in docker-compose.yml as well as to .env.production
  • Saving and testing need an identity check within the last 10 minutes, and every save notifies all super admins; see How system settings are protected. In production, pin at least APP_BASE_URL and SMTP_HOST with environment variables

Mail ​

SettingEnvironment variableDefault
Site URL (links in mails are built from it, never from the request's Host)APP_BASE_URLEmpty
SMTP serverSMTP_HOSTEmpty (no mail)
PortSMTP_PORT587
Encryption: auto / SSL/TLS / STARTTLS (auto = SSL/TLS on port 465)SMTP_SECURE (true = SSL/TLS, false = STARTTLS)Auto
Account / passwordSMTP_USER / SMTP_PASSWORDEmpty
Sender, e.g. Castor <noreply@example.com>MAIL_FROMThe account

Password reset by email can only be turned on once the SMTP server and the site URL are set, see Account security & settings.

File storage and uploads ​

SettingEnvironment variableDefault
Store in: local disk / S3-compatible storage (AWS S3, MinIO, Alibaba Cloud OSS, Tencent COS, Cloudflare R2)STORAGE_DRIVER (local / s3)Local disk
S3 endpoint (empty for AWS S3)S3_ENDPOINTEmpty
Region (auto for R2)S3_REGIONus-east-1
Bucket / access key / secret key (required for S3)S3_BUCKET / S3_ACCESS_KEY / S3_SECRET_KEYEmpty
Public URL (downloads redirect there; otherwise to a signed URL valid for about 10 minutes)S3_PUBLIC_URLEmpty
Addressing: auto / path style / virtual-hosted (auto = path style when an endpoint is set)S3_FORCE_PATH_STYLE (true / false)Auto
Max file size, also capped by BODY_LIMITUPLOAD_MAX_SIZE (bytes)10MB
Allowed file types; uploads are also checked for a file signature matching the extensionUPLOAD_ALLOWED_TYPES (comma-separated)jpg,jpeg,png,gif,webp,pdf,txt,csv,doc,docx,xls,xlsx,ppt,pptx,zip

Switching the storage only affects new uploads; existing files record where they live (including the bucket) and are still read from there. If new S3 endpoint or keys can't reach the original bucket, files stored there can no longer be read; the page says how many files are affected.

AI model ​

SettingEnvironment variableDefault
Service type: OpenAI-compatible / OpenAI / Anthropic / GoogleAI_PROVIDER (openai-compatible / openai / anthropic / google)OpenAI-compatible
API URL: required for OpenAI-compatible (e.g. https://api.deepseek.com/v1); leave empty for the others to use the official API, or enter a proxyAI_API_BASEEmpty
API keyAI_API_KEYEmpty
ModelAI_MODELEmpty
Enable the AI assistant: the chat assistant in the bottom-right corner that looks data up as the signed-in user and changes it once approved, see AI assistant; needs a configured modelNone (set on the page only)Off

AI Chat, AI Prompt Studio, AI Data Query and the AI assistant share these settings; calls go through the Vercel AI SDK without automatic retries. "OpenAI-compatible" covers every service with a /chat/completions API: DeepSeek, Qwen, Gemini's compatible endpoint, Ollama and more. When they are not set, those pages show a "not configured" notice; everything else is unaffected.

Sign-in lockout ​

SettingEnvironment variableDefault
Failed attempts before lockout (counted per IP and per username; per IP only in demo mode)LOGIN_MAX_FAILURES10
Lockout duration (minutes), also the window failures are counted inLOGIN_LOCKOUT_MINUTES15

Other security settings (two-step verification, password reset, password rules, session lifetime, rate limits) are only on the page, see Account security & settings.

Frontend (Web) ​

The frontend has no runtime environment variables. Dev server behavior is defined in apps/web/vite.config.ts:

SettingValue
Dev port5173
Proxy/api → http://localhost:5001, /ws → ws://localhost:5001 (the port is API_PORT when set, e.g. API_PORT=5011 for a second checkout whose backend runs with PORT=5011)
Path alias@ → apps/web/src

In production, the backend serves the frontend build directly and requests go to same-origin /api, so no extra configuration is needed.

User preferences in the browser (theme, appearance, language, tabs bar) are stored in localStorage / sessionStorage; see Theme & layout.

Docker ​

Variables read by compose ​

Put these in .env.production and pass them in with docker compose --env-file .env.production ...:

VariablePurposeDefault
POSTGRES_PASSWORDPassword of the PostgreSQL user castor_kitRequired
SECRET_KEYSee aboveRequired
ADMIN_PASSWORDSee aboveRequired
POSTGRES_RO_PASSWORDPassword of the AI SQL read-only accountRequired
NPM_REGISTRYnpm registry used to install dependencies while building the image (build argument)https://registry.npmmirror.com
APP_PORTPort mapped on the host (always 5000 inside the container)8080 (the config generated by setup.sh writes 5000)
ENABLE_TASK_SCHEDULERSee abovetrue
RUN_SCHEDULER_IN_WEBSee above. Defaults to true in compose, unlike the backend's own defaulttrue
SESSION_TTL_HOURSSee above8
COOKIE_SECURESee aboveEmpty (auto)
CORS_ORIGINSSee aboveEmpty
RATE_LIMIT_ENABLEDSee abovetrue
AI_PROVIDER / AI_API_KEY / AI_API_BASE / AI_MODELOptional: pin the AI model settings (see AI model); leave empty to configure them in System settingsEmpty
COMPOSE_DB_VOLUMEName of the database volume; can point to an existing volumecastor-kit_postgres_data
COMPOSE_DATA_VOLUMEName of the runtime data volume (mounted at DATA_DIR, holds uploads); can point to an existing volumecastor-kit_app_data

Based on these, compose sets the following automatically:

VariableValue
NODE_ENVproduction
DATABASE_URLpostgresql://castor_kit:<POSTGRES_PASSWORD>@db/castor_kit
AI_SQL_DATABASE_URLpostgresql://castor_kit_ro:<POSTGRES_RO_PASSWORD>@db/castor_kit

Other variables in .env.production (such as LOGIN_MAX_FAILURES) are not passed into the container automatically; add them under app.environment in docker-compose.yml if you need them.

Variables baked into the image ​

Set in the Dockerfile; you normally don't need to change them. The NPM_REGISTRY build argument defaults to https://registry.npmjs.org; builds through compose use the mainland-China mirror by default (see the table above).

VariableValue
NODE_ENVproduction
PORT5000
WEB_DIST_DIR/app/web
DATA_DIR/app/data
MIGRATIONS_DIR/app/drizzle

Toolchain ​

VariablePurpose
CASTOR_KIT_ROOTRepo root used by the MCP Server; derived from its own location by default

Released under the MIT License.