Deployment and production environment
This page follows the production path: prepare deliverables, start and check the service, connect a process manager and reverse proxy, then release or roll back. First complete the CLI project workflow and Build, then choose the deployment method for your environment.
Verify one production start
Prerequisites: a TypeScript API-only project with dependencies installed and the scaffold's src/routes/index.ts retaining / and /health. In the project root, run:
In another terminal, check both endpoints (use curl.exe in Windows PowerShell):
Both should return HTTP 200, and the health response should contain data.status: "ok". Press Ctrl+C in the server terminal, then confirm the process exits and releases the port. The fullstack scaffold uses /api/health; use your actual route if you changed it. The framework does not automatically add one universal health endpoint to every project.
The remaining configurations are scenario-specific fragments; do not paste them sequentially over an entire config file. Docker, PM2, Nginx and Kubernetes examples require their corresponding platforms, permissions and real service addresses. A successful local start does not verify those deployments.
Build production output
vext build
vext build refreshes generated and manifest tooling artifacts and compiles TypeScript sources to production JavaScript:
Without an explicit output directory, environment override or existing build record, output goes to dist/ and retains the source module layout. Examples on this page that use dist explicitly pass --outdir dist to both build and start. If you customize it, change both commands to avoid starting stale output.
--typecheck refreshes .vext/types/, src/types/generated/index.d.ts and .vext/manifest/, then runs tsc --noEmit and production compilation. This mapping illustrates optional business directories:
Compiler options
This table describes the backend compiler. The frontend has separate production defaults: browser minification on, browser source maps off, and SSR renderer minification off unless configured.
Compile exclusions
Production compilation excludes:
*.d.ts,*.d.mts,*.d.ctsdeclaration files*.test.*and*.spec.*test files__tests__/directoriesconfig/development.*,config/local.*andconfig/test.*
Compiler implementation
The backend build stage uses esbuild. Time depends on project size, plugins, source maps, filesystem and hardware. Measure your own CI or release build when setting a deployment budget.
Compilation injects process.env.NODE_ENV = "production", so source branches using that expression fold under production semantics. The runtime config profile is still chosen at startup: explicit --config, VEXT_CONFIG, compatible nonstandard NODE_ENV, then the build identity profile or production default. The corresponding compiled file must exist; changing only a profile name on the server is insufficient.
Complete delivery checklist
Output directory selection is explicit --outdir, then VEXT_BUILD_OUTDIR, then project build record, then dist. Pass --outdir explicitly in a new deployment environment with no local record. Do not copy only route and service JavaScript. Deliver external templates, upload directories, configuration and dynamically read resources at their actual paths. Preserve directory permissions and persistent data so the runtime user can write PID, uploads and app state.
The backend does not bundle npm dependencies. Put vextjs, adapters and business runtime dependencies in the application's dependencies. TypeScript start does not fall back to source; JavaScript projects still need source. See the deployment manifest for more boundaries.
Deliver the frontend in production
When frontend.enabled is true, vext build also writes the browser and SSR output to dist/client/: index.html, hashed assets, render-manifest.json, deploy-manifest.json and the default server/renderer.cjs. Backend output retains the source directory layout under dist/; there is no fixed top-level dist/server/ to deploy.
Same-origin deployment
First configure frontend role directories and render mode and confirm the build passes. Same-origin static assets do not need a CDN URL:
vext start validates frontend output before listening, then serves static assets and SSR pages according to configuration. CSR SPA fallback additionally requires configured scopes; unknown URLs do not all automatically return index.html.
CDN deployment and upload
Use a CDN only when it will own immutable browser assets. Set an absolute
frontend.deploy.assetBaseUrl, keep HTML/SSR on the Node service, then review
the upload plan before executing it:
deploy-manifest.json contains uploadable JS, CSS, imported media, and
public/** files with sha256/SRI metadata. It deliberately excludes SSR HTML
and source maps. Keep frontend.deploy.upload.stateFile outside the output
directory so an ordinary build cannot erase incremental-upload state.
Only filesystem and mock adapters are built in. filesystem creates a local staging tree; a real cloud provider requires an explicit custom adapter and application-configured dependencies and credentials. See Build and Deploy for the full sequence and Frontend Configuration for every field.
Start production service
Start directly
TypeScript projects require complete, valid build output; use vext dev during development. Do not hide missing production output by uploading source or installing tsx. Keep a fixed working directory when deploying: PID files and relative paths depend on it.
Environment variables
Shutdown and timeout budget
On graceful shutdown, stop accepting traffic, then wait for in-flight requests and onClose cleanup. Single-app shutdown.timeout is in seconds, default 10. Cluster reload.shutdownTimeout is in milliseconds, default 10000. Allow extra time in the external process manager; internal 10 seconds and container or PM2 30 seconds are starting points to adjust against real request and connection behavior.
On Unix, the CLI forwards SIGINT and SIGTERM. On Windows it uses child-process IPC for signals with a 15-second fallback. Forcibly killing a Windows process does not invoke application onClose. See Cluster for Cluster shutdown, PID and SIGHUP platform limits.
Docker deployment
Dockerfile
This Dockerfile is for a TypeScript API-only project with default dist, without frontend, external templates or additional build resources. The build context needs the app's package.json, lockfile, tsconfig.json and src. Copy project preload or other build inputs if present. For frontend projects, include the role directories and output described above.
The health route belongs to this page's API-only scaffold. Use GET rather than assuming HEAD support. Exec-form CLI startup avoids an extra npm/shell wrapper affecting signal delivery. EXPOSE only declares the port; publish it with the runtime command below.
.dockerignore
Docker Compose
The application must explicitly read the database variable. If no earlier config layer defines database, provide the complete value in production config, for example:
depends_on: service_healthy handles initial ordering. If Mongo becomes unavailable later, the app still needs retries, readiness checks and operational recovery. Keep the Mongo volume outside the app image. A container healthcheck alone does not make ordinary Docker restart an unhealthy process that is still running. See Dockerfile health checks and Compose startup order.
Build and run
The standalone docker run database address assumes Docker Desktop provides host.docker.internal. Linux hosts need an actually reachable address or explicit host-gateway setup. Compose uses the mongo service name.
Nginx reverse proxy
Basic configuration
Prepare the domain, certificate and reachable backend port. This example proxies an HTTP API. Enable trustProxy only when the application needs forwarded addresses, and restrict backend access to trusted proxies.
Ordinary API requests should not all send Connection: upgrade. Configure conditional Upgrade only if your adapter actually supports WebSocket; see the Nginx WebSocket guide. Streaming SSE endpoints also need their own buffering and timeout settings. The /static/ alias is a separate static directory example and does not automatically correspond to Vext's hashed frontend assets.
After editing, run nginx -t, reload Nginx only if it passes, and check external HTTPS, forwarded headers, upload limits and the actual health route.
Multi-instance load balancing
PM2 process management
Installation
Ecosystem configuration
This example targets a Unix host. Create the writable log directory first and replace cwd with your real release directory:
PM2 manages the Vext CLI parent process; application work runs in its child process or Cluster Workers. Do not treat the PM2 parent's CPU and memory values as complete Worker metrics. Monitor business processes and containers separately.
Keep PM2's default SIGINT shutdown path. Do not set shutdown_with_message: true: it sends a "shutdown" message that this CLI does not handle. See PM2's shutdown mechanism and configuration fields.
Common PM2 commands
Enable VextJS's built-in Cluster with cluster.enabled: true or VEXT_CLUSTER=1; cluster.workers selects the Worker count. start does not support --cluster or --workers flags. When using built-in Cluster, keep PM2 instances: 1 and let VextJS manage the Workers. Rolling replacement still depends on the platform and readiness conditions below. See Cluster.
PM2's single fork example provides no multi-instance rolling guarantee. Even with Vext Cluster, keep one outer instance. SIGHUP reload must target the Vext Master PID; do not assume pm2 reload invokes Vext's rolling protocol. Configure shared Sessions, rate limits and connection pools separately.
Log collection
JSON log format
Set pretty: false explicitly for newline-delimited production JSON logs. CLI startup notices may share the same stream, so collectors must distinguish them. Do not add PM2 timestamp prefixes that break JSON parsing. These fields illustrate default ISO timestamps; see Logger and Access Log for actual request log formats:
Configure log level
Log collection plan
Solution 1: File + Filebeat → ELK
This uses Filebeat filestream and ndjson. Handle parser errors for mixed startup text and add the authentication, index policy and log rotation required by your deployment. The old log input is deprecated.
Option 2: Docker log → Loki
Install and configure the driver on the Docker host using the Loki Docker driver instructions. This fragment only selects the driver; it does not install the driver or deploy Loki.
Option 3: stdout → Cloud native
In platforms such as Kubernetes / AWS ECS / Cloud Run, output directly to stdout, which is automatically collected by the platform:
Health Check
Implement health check endpoint
If the scaffold already has /health, replace its existing handler rather than registering it twice. Keep other routes in src/routes/index.ts.
index.ts adds no filename prefix. If you move this to health.ts, register "/" there; registering "/health" would create /health/health. The default response wrapper puts status under data.status. Configure bypasses for global auth, middleware or caching as needed; disabling rate limits alone does not bypass them.
- Liveness asks whether the process responds. Do not restart every instance repeatedly for a brief database outage.
- Readiness asks whether critical dependencies work. Return 503 on failure and 200 after recovery.
app.db !== undefineddoes not prove a live connection. An initializedreq.app.db.clientcan perform a real ping with a timeout; first configure Database. - Cluster: one HTTP probe reaches only one Worker.
vext statuschecks/health, but does not replace all-Worker, dependency and business checks. Exit code 0 is insufficient as a deployment health gate.
After checking the 200 path, deliberately make a critical dependency unavailable, confirm readiness returns 503 and the instance leaves traffic, then restore and verify again.
Kubernetes probe configuration
Put this fragment in a Deployment's spec.template; a complete resource also needs metadata, selector, replicas and image pull configuration. Consider startupProbe based on actual startup time. Replicas, readiness and a termination grace period work together to drain traffic; a readiness probe alone does not guarantee zero interruption. See Kubernetes probes.
Abnormal crash notification (onFatalError)
VextJS has a built-in process-level exception catching mechanism. When an uncaughtException or unhandledRejection occurs, the framework will:
- Record
fatallevel logs - Call the user-configured
onFatalErrorcallback (if any) - Perform graceful shutdown (onClose hooks clean up resources)
process.exit(1)Exit the process
Configure onFatalError
Add the onFatalError callback in the shutdown configuration to access alarm notifications:
Replace the Webhook URL with your own. The target service determines notification format and access. Each following snippet replaces the same onFatalError callback. Give external requests a timeout and check the response; a non-2xx HTTP response is not a successful notification.
Enterprise WeChat Webhook Example
Slack Webhook Example
Generic HTTP Webhook Example
Notes
uncaughtException and unhandledRejection occur outside the HTTP middleware execution chain (such as exceptions in scheduled tasks and event listeners), and middleware cannot catch such errors. Therefore, process level event listeners must be registered in the framework bootstrap layer.
Security hardening
Production environment list
Environment variable management
VextJS does not automatically parse .env files and does not bundle an implicit
dotenv loader. Values read through process.env must be injected by the OS,
shell, process manager, container platform, CI/CD system, secret manager, or a
loader that your application explicitly owns. The scaffold still ignores
.env* files to reduce accidental commits when external tools create them; that
Git protection does not mean Vext loads those files.
Performance optimization
Node.js Parameters
Node's default heap limit depends on version, platform and available memory; it is not a fixed 1.5 GB. This flag limits V8 old space, not process RSS or the entire container.
Source Map
Backend build emits .js.map files by default, but esbuild's external mode does not write sourceMappingURL. Setting only NODE_OPTIONS=--enable-source-maps therefore does not guarantee that current stack traces map to TypeScript. Associate JS and maps in your diagnostic platform and verify with an actual exception; see Build Source Map boundaries.
Cluster multi-process
Merge these settings into existing production config, then build and start. Validate a fixed Worker count against real CPU, memory and connection pool budgets. With "auto", the framework uses detected available CPUs, capped at 64.
See Cluster multi-process for details.
Connection pool optimization
Each Worker creates its own pool. Estimate the maximum connection budget as pool limit × Workers × replicas, plus monitoring and other processes. Fetch retries apply only to supported idempotent methods and replayable requests; include every attempt timeout and backoff in the total budget. See HTTP Client.
Deployment process suggestions
CI/CD pipeline
Gradual rollout
- Build an image with a unique version tag, retaining the current image and matching configuration.
- Deploy the new version on a new port or replica, then directly verify real health, readiness and business requests.
- Check and reload proxy configuration before introducing weighted traffic. A weight is a scheduling ratio, not an exact count per ten requests.
- Observe errors, latency, dependency load and session compatibility. Increase traffic after validation; switch back to the old instance on failure.
- Drain the old instance, wait for in-flight requests and long connections, then stop it. Database schema changes need their own compatibility and rollback plan.
Supply the actual database connection config above when this app needs a database. Replace the Nginx upstream with:
Vext Cluster reload does not update Master configuration, publish images, change database schemas, or preserve every long connection. sticky: "ip" routes TCP peer addresses to stable Worker slots inside one instance. Proxies/NAT can create hotspots, and failures or rolling replacement do not migrate in-memory sessions. External instance affinity does not provide Worker affinity behind a shared port. With zero Workers and no pending replacement, the standard host records failed state, cleans its PID, and exits 1 for supervisor recovery. Remaining healthy Workers keep running, so monitor ready capacity and business health.
Monitor alarms
Key monitoring indicators
This table is only a starting point for alert design. Adjust it for business SLOs, baselines and resource limits; these values are not framework metrics or performance guarantees.
Prometheus Metrics Endpoint
Initialize a real Prometheus Exporter following the OpenTelemetry example, and configure the collector for its actual port and path. An ordinary res.json() response is not a Prometheus metrics endpoint.
Shared resources across services
Each service uses its own cwd, profile, business port, generated outputs and persistent data directory. Different ports do not isolate same-domain cookies. Choose cookie names, path/domain and Session store namespaces according to whether sessions should be shared. Configure isolation explicitly when separate Sessions are needed; changing only the port is insufficient.
Response caching, MonSQLize query caching, Session and rate limiting are different systems. Check key prefixes/namespaces, TTL units and invalidation scope before sharing stores. Vext does not rename user configuration based on inferred intent. Estimate connections as each process's pool limit × workers × services, plus independent pools/proxies; actual external limits need deployment evidence.
Model registrations in multiple apps within one process have owners: equivalent definitions can share a key; conflicting definitions fail before registration; closing one app releases only its references. Registry keys and databases/pools are distinct; see Database. Separate processes have separate registries but may still share external stores.
Deploy scheduled jobs
Scheduled jobs automatically start after HTTP application readiness, sharing its services and shutdown lifecycle. Active jobs in built-in Cluster require jobs.redis; other replicas also need shared Redis and matching schedule definitions. Namespace is automatic from package name, profile and runtime mode; ordinary deployments need no override. Redis failures skip triggers without fallback. Restart registers only future points. See Jobs.
Publish the documentation site
When maintaining this VextJS repository, documentation sources remain in devcodex-labs/vextjs. The same commit is published to two documentation sites. These are separate from application deployment above.
Keep GitHub Actions selected in the source repository's Settings → Pages → Source. Complete this setup once for the additional organization site; subsequent builds publish automatically:
- Create the public repository
vextjs.github.ioin thevextjsorganization and initialize itsmainbranch. This repository is reserved for build output: publishing replaces the site files at its root, removes stale output, and preserves Git commit history. - Create a dedicated SSH Deploy Key for that repository. Add the public key in the site repository's
Settings → Deploy keyswith Allow write access enabled. Save the private key asVEXT_DOCS_DEPLOY_KEYin the source repository'sSettings → Secrets and variables → Actions. Never put the private key in source code, documentation, or chat messages. - In the site repository's
Settings → Pages, select Deploy from a branch, branch main, and folder /(root). The repository name determines the organization root URL; no CNAME is needed.
.github/workflows/docs.yml builds with Node.js 22 and sets VEXT_DOCS_BASE and VEXT_DOCS_SITE_URL separately for the paths and URLs in the table. Both artifacts are validated and uploaded separately to keep root-path assets distinct from /vextjs/ assets. Automatic publishing follows a successful source-repository main push CI and checks out that exact SHA.
Each site calls .github/workflows/docs-build.yml independently and starts its publishing job after its own complete documentation publishing checks pass: the source repository uses a Pages artifact and the built-in GITHUB_TOKEN; the organization site uses VEXT_DOCS_DEPLOY_KEY to push its output. A build or publishing failure blocks only that site's publication in the current run; the other site can still publish after passing its own checks. Until that key is configured, the workflow skips organization publication with a notice and continues publishing the source site. Before publishing, each job checks the current source main commit and skips builds superseded by a later commit. Version tags continue to trigger automatic releases through release.yml.
To rebuild and republish both sites, select main in the source repository's Actions → Deploy Docs → Run workflow. This manual entry also runs the complete documentation checks; other branches cannot publish to the production site through it. After the organization site's push succeeds, wait for the target repository's Pages publishing job to finish before checking the updated site.
Next step
- Learn about Cluster Multi-Processing to take full advantage of multi-core CPUs
- See OpenTelemetry Access for full observability
- Learn Nacos access to implement microservice registration discovery
- Explore the environment configuration override mechanism in Configuration