Runtime Environments and Flavors
This guide explains how runtime environments and flavors work in Vona within the Cabloy monorepo.
Why this model exists
Vona does not treat backend runtime selection as a single switch.
In practice, backend behavior is chosen by combining:
- runtime environment
- flavor
That gives Cabloy enough room for development, testing, Playground, Docker, CI, and deployment-specific behavior without flattening everything into one axis.
Runtime environments
Vona provides three main runtime environments:
| Name | Description |
|---|---|
test | testing environment |
dev | development environment |
prod | production environment |
How runtime environment is activated in the current repo
In Cabloy Basic, the active mode is usually implied by which command you run.
Root-level contributor workflow
npm run dev
npm run test
npm run build
npm run startThese root scripts delegate to the backend or frontend layer as needed, so they are the preferred monorepo-facing entrypoints.
Direct Vona workflow
Representative backend-side scripts include:
cd vona && npm run dev
cd vona && npm run dev:one
cd vona && npm run test
cd vona && npm run cov
cd vona && npm run db:reset
cd vona && npm run build
cd vona && npm run build:docker
cd vona && npm run start
cd vona && npm run start:oneIn the current repo, those scripts map naturally to:
devcommands -> development modetest/cov/db:reset-> test modebuild/start-> production mode
Flavors
For more specific scenarios, Vona adds the flavor dimension.
The combination of runtime environment and flavor lets the framework support more precise operational contexts without inventing a separate environment for every case.
Built-in flavors used in this repo
| Name | Description |
|---|---|
normal | default contributor/runtime flavor |
play | used by the Playground workflow |
docker | used for Docker-oriented build/runtime output |
ci | used for CI-oriented production variants |
Representative current scripts include:
cd vona && npm run play
cd vona && npm run build
cd vona && npm run build:docker
cd vona && npm run startbuild:docker produces Docker-oriented output. Run that output through the Docker Compose workflow rather than a direct Vona start:docker command.
These scripts show that the same prod mode can still branch into different output/runtime shapes through flavor.
What the CLI injects automatically
The current Vona CLI does not only read env files. It also injects key meta variables for the running process.
The important ones are:
NODE_ENVMETA_MODEMETA_FLAVORSERVER_WORKERS
A practical interpretation is:
META_MODEis the backend runtime mode source of truthMETA_FLAVORcarries the flavor dimensionNODE_ENVis derived from mode (test,development, orproduction)SERVER_WORKERSis normalized so the runtime always has an explicit worker count
In the current CLI behavior:
- prod defaults
SERVER_WORKERSto CPU count when not provided - non-prod defaults
SERVER_WORKERSto1
Curated built-in env variable catalog
The current repo uses many env variables, but these are the most important ones for understanding the runtime/config family.
| Variable family | Representative variables | Why it matters |
|---|---|---|
| Runtime meta | META_MODE, META_FLAVOR, NODE_ENV | chooses the active runtime and flavor shape |
| Worker/runtime process | SERVER_WORKERS | controls worker count and is normalized by the CLI/bootstrap path |
| HTTP server | SERVER_LISTEN_HOSTNAME, SERVER_LISTEN_PORT, SERVER_LISTEN_DISABLE, SERVER_SERVE_PROTOCOL, SERVER_SERVE_HOST, SERVER_GLOBALPREFIX | controls listen/serve behavior and URL shaping |
| CORS | CORS_WHITE_LIST | supplies the default allowed-origin whitelist for the a-security:cors middleware |
| Database | DATABASE_DEFAULT_CLIENT, DATABASE_CLIENT_SQLITE3_FILENAME, DATABASE_CLIENT_PG_*, DATABASE_CLIENT_MYSQL_* | controls datasource defaults and concrete client connection settings |
| Redis | REDIS_DEFAULT_HOST, REDIS_DEFAULT_PORT, REDIS_DEFAULT_DB | controls the backend Redis baseline used by queue, cache, broadcast, and related capabilities |
| Logger | LOGGER_DIR, LOGGER_ROTATE_* | controls log path and rotation behavior |
Use this page as the runtime-facing overview, then inspect the current app config when you need to see how those values are translated into backend config.
Public server origin
SERVER_LISTEN_HOSTNAME and SERVER_LISTEN_PORT control where the Vona process binds. They do not define the public URL that Vona emits.
When configured, SERVER_SERVE_PROTOCOL and SERVER_SERVE_HOST define Vona's canonical public origin for backend APIs and backend-hosted SSR sites. If either serve value is not configured, Vona resolves the effective protocol and host from the current proxy-aware request context; background work that needs a stable backend public origin should configure both serve values explicitly.
A browser consumer can be deployed on a different origin and pathname. Account password-set and password-reset delivery therefore does not derive its destination from SERVER_SERVE_*, SSR Site metadata, publicPath, siteId, Referer, or browser-selected mount data: Zova supplies the complete token-free absolute public page URL, and Vona validates only HTTP(S), absence of userinfo/query/fragment, and an exact trusted origin. checkOriginExact permits the exact current origin when protocol, host, and effective port all match; request or proxy Host values are not a general cross-origin allowlist and cannot authorize lookalike, suffix, alternate-scheme, or alternate-port destinations. Other production consumer origins must exactly match an explicit a-security:cors whiteList entry. In dev/test, exact credential-link authorization additionally accepts different ports only when both API and consumer hostnames are loopback (localhost, 127.0.0.1, or ::1). Vona preserves the frontend pathname and alone adds the raw token as the token URL query parameter. Query transport reaches the initial request; Referrer-Policy and Vona, edge, or APM query-log redaction are intentionally outside this flow's current scope. Normal CORS defaults to an empty whiteList; configure CORS_WHITE_LIST with the permitted browser origins, or explicitly configure a-security:cors. In dev/test, loopback requests may cross ports; elsewhere, cross-origin CORS requires an allowed entry. Normal CORS wildcard and suffix matching remain available when configured but are not credential-link authorization.
Configure browser CORS origins
CORS_WHITE_LIST supplies the default whiteList for the app-wide a-security:cors middleware. It controls normal HTTP CORS and the built-in WebSocket origin check; it does not authenticate or authorize requests.
Set it to a comma-separated list of allowed browser origins. Use complete origins, including the scheme and a non-default port when applicable:
CORS_WHITE_LIST=https://app.example.com,https://admin.example.comCORS_WHITE_LIST=https://app.example.com,http://localhost:5173When CORS_WHITE_LIST is absent or empty, Vona retains the closed empty-whitelist default. The existing dev/test loopback exception remains available for local development, so ordinary loopback clients on different ports do not require this setting.
Set deployment-specific origins in the active vona/env/.env* layer or inject them through the deployment environment. See Env-file resolution and precedence for the active-file chain and precedence rules. Applications that need middleware behavior beyond this default origin list can configure a-security:cors explicitly.
Normal CORS may use its configured wildcard or suffix matching behavior. Do not use that behavior to authorize account credential links: checkOriginExact(...) accepts only an exact normalized HTTP(S) origin and rejects wildcard and suffix entries.
How to determine runtime metadata in code
Via environment variables
process.env.META_MODE === 'test';
process.env.META_MODE === 'dev';
process.env.META_MODE === 'prod';
process.env.META_FLAVOR === 'normal';
process.env.META_FLAVOR === 'docker';
process.env.META_FLAVOR === 'ci';Using environment variables is especially useful when behavior needs to participate in build-time replacement or tree-shaking.
Via config
app.config.meta.mode === 'test';
app.config.meta.mode === 'dev';
app.config.meta.mode === 'prod';
app.config.meta.flavor === 'normal';
app.config.meta.flavor === 'docker';
app.config.meta.flavor === 'ci';Via simplified helpers
app.meta.isTest;
app.meta.isDev;
app.meta.isProd;A practical rule is:
- use
process.envwhen the logic is environment-driven and may affect bundling or compile-time replacement - use
app.config.metaorapp.metawhen the logic belongs to normal runtime behavior inside the running backend
Env-file resolution and precedence
This page owns the env-file and mode/flavor precedence view. For config surfaces and config layering, see Config Guide. For the fuller instance-aware merge view used by request-scoped behavior, see Multi-Instance and Instance Resolution.
In the current repo, env values are loaded from the vona/env/ directory.
Representative files include:
.env.env.dev.env.dev.play.env.test.env.prod.env.prod.ci.env.prod.docker.env.local.env.prod.local.env.prod.docker.local.env.prod.normal.local
The current loader builds the env-file chain from runtime metadata shaped like:
modeflavorlocal
That means the loader can cascade from general files to more specific files such as:
.env.env.prod.env.prod.docker.env.prod.docker.local
The important rule is:
.localvariants are treated as highest-priority local overrides
Representative effective chains in the current repo look like this:
dev + normal->.env->.env.dev->.env.localdev + play->.env->.env.dev->.env.dev.play->.env.localprod + docker + local->.env->.env.prod->.env.prod.docker->.env.local->.env.prod.local->.env.prod.docker.local
One more practical detail matters during bootstrap:
- after the env files are prepared, already-present
process.envvalues still win when the runtime assembles the final env object
So when you document or debug backend configuration, do not assume one flat .env file. Inspect the active mode, flavor, local override files, and any externally injected environment variables together.
Custom flavors
Flavors are not limited to the built-in ones. You can introduce your own flavor for needs such as:
- customer-specific behavior
- deployment-specific behavior
- project-specific behavior
- organization-specific behavior
Example:
npm run dev -- --flavor=customAAnd in code:
process.env.META_FLAVOR === 'customA';
app.config.meta.flavor === 'customA';Type support for custom flavors
Custom flavor names can also be added to type definitions for better editor support.
Representative pattern:
declare module '@cabloy/module-info' {
export interface VonaMetaFlavorExtend {
customA: never;
}
}How runtime metadata becomes backend config
Runtime env values do not stop at process.env.
In the current Vona app config, they are folded into config such as:
config.meta.modeconfig.meta.flavorconfig.server.*config.database.defaultClientconfig.logger.*config.redis.*
This is why runtime environment and config should be read together, not as separate concerns.
For the config-layering view, also see Config Guide.
Relationship to startup, instance, and datasource behavior
Runtime environment and flavor are not only configuration concerns. They also shape:
- startup enable/disable rules
- startup hook behavior in different modes
- distributed capability activation
- datasource defaults
- local-vs-prod operational differences
Read this guide together with:
- Config Guide
- Backend Startup Guide
- Model Guide
- Multi-Database and Datasource Guide
- Queue Guide
- Schedule Guide
- Broadcast Guide
Implementation checks for runtime and flavor changes
When evaluating backend runtime or configuration guidance, do not assume that dev/test/prod is the whole story.
Also inspect:
- which repo command actually drives the workflow
- whether the active behavior depends on flavor
- whether
.localoverrides may be changing the effective env values - whether the runtime check should use
process.env,app.config.meta, orapp.meta - whether a datasource, startup, or distributed feature is mode- or flavor-sensitive
These environment differences also matter for operational concerns such as log directories, log levels, worker count, and runtime output layout; see Logger Guide.