Vix.cpp v2.6.0 is here Read the blog
Skip to content

From Local to Production

A Vix application should move from local development to production without becoming a different project.

Local development should be simple:

bash
vix dev

Production should be repeatable:

bash
vix deploy

The app stays the same.

The environment changes.

The workflow becomes stricter.

The model

The path from local to production is:

txt
local app
  -> local build
  -> local checks
  -> production config
  -> service
  -> proxy
  -> health checks
  -> logs
  -> deploy

A serious app should not depend on random manual steps.

The production path must be written down in config and commands.

Local is for feedback

Local development is about speed.

The normal local loop:

bash
vix dev

This gives:

txt
watch files
rebuild when needed
restart app
show runtime output

For one manual run:

bash
vix run

For one file:

bash
vix run main.cpp

Local work should be fast, but not careless.

You still need checks before production.

Production is for repeatability

Production is not a terminal left open with:

bash
vix run

Production should be controlled by:

txt
systemd service
Nginx proxy
environment file
health checks
logs
deploy workflow

Vix provides commands for these pieces:

bash
vix service
vix proxy nginx
vix health
vix logs
vix deploy

The goal is not to hide production.

The goal is to make it repeatable.

The production contract

A production-ready Vix app should answer:

txt
How is it built?
How is it started?
Which user runs it?
Which env file is used?
Which port does it listen on?
Which domain exposes it?
How is HTTPS configured?
How is health checked?
How are logs read?
What happens when deploy fails?

If the project cannot answer these questions, production is not ready.

Build before production

Before production, build clearly.

bash
vix build --preset release

Then validate:

bash
vix check --tests

A good release flow:

bash
vix fmt --check
vix build --preset release
vix check --tests

Do not deploy a project that cannot pass its own checks.

Dependencies before production

After cloning on a server:

bash
vix install

This installs exact locked dependencies from vix.lock.

Do not use update for deployment setup.

txt
vix install = reproduce locked dependency state
vix update = change dependency state

Production should be reproducible.

That starts with the lockfile.

Environment files

Local apps often use:

txt
.env
.env.example

Production apps should also define required variables:

txt
production.env.required

Example .env.example:

dotenv
APP_ENV=development

SERVER_HOST=127.0.0.1
SERVER_PORT=8080
SERVER_TLS_ENABLED=false

VIX_LOG_LEVEL=info
VIX_LOG_FORMAT=kv

DATABASE_ENGINE=sqlite
DATABASE_DEFAULT_NAME=./data/app.db

JWT_SECRET=change-me
SESSION_SECRET=change-me

Example production.env.required:

txt
APP_ENV
SERVER_HOST
SERVER_PORT
VIX_LOG_LEVEL
DATABASE_ENGINE
JWT_SECRET
SESSION_SECRET

Check local env:

bash
vix env check

Check production env:

bash
vix env check --production

Show values only when needed:

bash
vix env check --production --show-values

Secrets are always masked.

Production config in vix.json

Production should be configured in vix.json.

The structure can include:

txt
production.service
production.proxy
production.health
production.logs
production.deploy

Example:

json
{
  "production": {
    "service": {
      "name": "api",
      "user": "vix",
      "working_dir": "/home/vix/apps/api",
      "command": "vix run",
      "env_file": "/home/vix/apps/api/.env"
    },
    "proxy": {
      "domain": "api.example.com",
      "http_port": 8080,
      "websocket_enabled": true,
      "websocket_port": 9090,
      "websocket_path": "/ws",
      "tls": {
        "enabled": true,
        "certificate": "/etc/letsencrypt/live/api.example.com/fullchain.pem",
        "certificate_key": "/etc/letsencrypt/live/api.example.com/privkey.pem"
      }
    },
    "health": {
      "service": "api",
      "local": "http://127.0.0.1:8080/health",
      "public": "https://api.example.com/health",
      "websocket": "wss://api.example.com/ws"
    },
    "logs": {
      "service": "api",
      "nginx_access": "/var/log/nginx/api.access.log",
      "nginx_error": "/var/log/nginx/api.error.log"
    }
  }
}

The values must match the server.

Do not put fake production config in a real deploy.

Service layer

The service layer runs the app as a system service.

Use:

bash
vix service init

Then:

bash
vix service status
vix service restart
vix service stop

The service should know:

txt
service name
working directory
command
user
environment file
restart policy

The app should not require someone to start it manually after reboot.

Why systemd matters

On Linux servers, systemd gives:

txt
startup on boot
restart on failure
status inspection
journal logs
controlled stop and restart

A backend app should run as a managed service.

That gives a clean operational model:

txt
code changes through deploy
runtime through service
logs through journal
public traffic through proxy

Proxy layer

The proxy layer exposes the app publicly.

For Nginx:

bash
vix proxy nginx init

Check config:

bash
vix proxy nginx check

Reload:

bash
vix proxy nginx reload

Issue or renew certificate:

bash
vix proxy nginx certbot

The proxy should map public traffic to the local app.

Example:

txt
https://api.example.com
  -> http://127.0.0.1:8080

For WebSocket:

txt
wss://api.example.com/ws
  -> ws://127.0.0.1:9090

TLS model

Production traffic should use HTTPS.

The TLS model:

txt
port 80 redirects to 443
port 443 uses certificate and key
Nginx forwards traffic to local app

The app can stay local:

txt
127.0.0.1:8080

Nginx handles public TLS.

This keeps the app simple and the public boundary clear.

WebSocket proxying

If the app uses WebSocket, production config must say it.

Important values:

txt
websocket enabled
public WebSocket URL
local WebSocket host
local WebSocket port
WebSocket path
timeout
heartbeat

Check WebSocket:

bash
vix ws check

Or through health:

bash
vix health websocket

WebSocket should not be assumed healthy just because HTTP works.

Health checks

Health checks prove the app is usable.

Run all checks:

bash
vix health

Local endpoint:

bash
vix health local

Public endpoint:

bash
vix health public

WebSocket endpoint:

bash
vix health websocket

A health check should report:

txt
target
URL
expected status
actual status
response time
max response time
healthy yes or no
error when present

Production without health checks is blind.

The health route

A backend should expose:

txt
GET /health

Example response:

json
{
  "ok": true,
  "service": "api",
  "status": "healthy"
}

This route should be fast.

It should not depend on heavy work.

If it checks database connectivity, make that intentional.

Logs

Production logs must be easy to read.

Use:

bash
vix logs

Application logs:

bash
vix logs app

Proxy logs:

bash
vix logs proxy

Error logs:

bash
vix logs errors

Follow logs:

bash
vix logs app --follow

Read recent logs:

bash
vix logs errors --lines 100

Logs should help answer:

txt
Did the service start?
Did it crash?
Did Nginx route traffic?
Did health checks fail?
Which error happened first?

Deploy workflow

Deployment should be a sequence of clear steps.

Run:

bash
vix deploy

Preview:

bash
vix deploy --dry-run

Verbose:

bash
vix deploy --verbose

Skip pull:

bash
vix deploy --no-pull

Skip tests:

bash
vix deploy --no-tests

A deployment can include:

txt
git pull
build
tests
service restart
local health check
public health check
proxy check
proxy reload
logs on failure
rollback

This is the production workflow in one command.

Deploy config

Example:

json
{
  "production": {
    "deploy": {
      "pull": true,
      "branch": "main",
      "build": "vix build --preset release",
      "tests": true,
      "test_command": "vix check --tests",
      "service": "api",
      "health_local": true,
      "health_public": true,
      "proxy_check": true,
      "proxy_reload": true,
      "logs_on_failure": true,
      "log_lines": 120,
      "rollback": true
    }
  }
}

Each step should be visible.

Deployment should not feel like a black box.

Dry run first

Before trusting production deploy:

bash
vix deploy --dry-run

A dry run should show the planned steps without executing them.

Use it when:

txt
setting up a new server
changing service config
changing proxy config
changing deploy config
debugging production flow

Dry run is not just for beginners.

It prevents avoidable mistakes.

Rollback

Rollback should exist because deploys can fail.

A deploy can fail because:

txt
build failed
tests failed
service restart failed
health check failed
proxy config failed
public endpoint failed

If rollback is enabled, Vix should attempt to restore the previous working state.

The model:

txt
deploy new version
  -> check health
  -> if failed, restore previous state

Rollback should be explicit in config.

Logs on failure

When deployment fails, the next step is logs.

Config:

json
{
  "production": {
    "deploy": {
      "logs_on_failure": true,
      "log_lines": 120
    }
  }
}

This lets deploy show useful context immediately.

A failed deploy without logs forces manual guessing.

Local checks before deploy

Before deployment:

bash
vix fmt --check
vix build --preset release
vix check --tests
vix env check --production

Then:

bash
vix deploy --dry-run
vix deploy

This sequence is simple and serious.

Server setup order

For a fresh server, use this order:

bash
vix doctor
vix install
vix env check --production
vix build --preset release
vix service init
vix proxy nginx init
vix health
vix deploy --dry-run
vix deploy

This order prevents confusion.

Environment before service.

Service before proxy health.

Proxy before public health.

Deploy after the pieces are ready.

vix doctor in production

Use:

bash
vix doctor

With online check:

bash
vix doctor --online

Doctor checks the environment.

Use it when:

txt
tools are missing
Vix behaves strangely
a new server was prepared
upgrade may be needed
build or run fails unexpectedly

vix doctor diagnoses.

It does not fix everything automatically.

vix info in production

Use:

bash
vix info

This shows:

txt
Vix version
Vix root
registry path
store path
global packages
artifact cache
disk usage
local state

Use it before cleanup.

Use it when dependency state is unclear.

Use it when registry or store paths matter.

Registry in production

If production needs project dependencies:

bash
vix registry sync
vix install

If the registry is already synced but outdated:

bash
vix registry sync

If install fails because a package is not found:

bash
vix registry sync
vix install

The registry index is metadata.

The store is package content.

The project lockfile chooses exact versions.

Store cleanup in production

Check store path:

bash
vix store path

Preview cleanup:

bash
vix store gc --project --dry-run

Run cleanup:

bash
vix store gc --project

Be careful.

Project-scoped GC can remove cached packages not referenced by the current project lockfile.

Always preview first.

Production database

For SQLite apps:

bash
vix db status
vix db migrate
vix db backup

Before dangerous deploys:

bash
vix db backup

For migration workflows:

bash
vix orm status --db api --dir ./migrations
vix orm migrate --db api --dir ./migrations

Database state is production state.

Treat it carefully.

Backups

A production app that writes data needs backups.

For SQLite:

bash
vix db backup

This can copy:

txt
database file
WAL file when present
SHM file when present

Backups are not optional for real systems.

If the app stores important data, backup must be part of the operational model.

Static files

If the app serves static files, the production model should define where they live.

Example:

txt
public/
assets/
uploads/

Generated build artifacts should not be mixed with user uploads.

A simple separation:

txt
public = versioned static files
uploads = runtime user files
dist = packaging output
build = build output

This prevents accidental deletion during cleanup.

Runtime arguments in production

Local runtime arguments can be passed with:

bash
vix run -- --port 8080

In production, prefer config and env files.

The service should define the command clearly.

Example:

txt
vix run -- --port 8080

But do not hide important values in random shell history.

Put stable production values in the service config or environment file.

Production user

Do not run production apps as root unless there is a real reason.

A production service should define:

txt
user = vix
working directory = /home/vix/apps/api

The service user should own only what it needs.

This limits damage when something goes wrong.

Ports

A simple production model:

txt
app HTTP port: 8080
app WebSocket port: 9090
Nginx public HTTP: 80
Nginx public HTTPS: 443

The app listens locally.

Nginx exposes public traffic.

This keeps the public surface small.

Firewall model

The public server should expose only what is needed.

Usually:

txt
22 for SSH
80 for HTTP redirect
443 for HTTPS

Application ports like 8080 and 9090 should normally stay local.

This depends on your deployment setup, but the default should be conservative.

Production output

Production commands should print useful summaries.

For deploy:

txt
App
Branch
Pull yes or no
Build command
Tests enabled or disabled
Service name
Health checks
Proxy check
Logs on failure
Rollback
Dry run
Verbose

For proxy init:

txt
App
Domain
HTTP upstream
WebSocket upstream
TLS enabled or disabled
Site file
Enabled path
Certificate paths

For health:

txt
Target
URL
Expected status
Actual status
Time
Healthy yes or no

This is how production stays explainable.

Local to production example

A full backend flow:

bash
vix new api --template backend
cd api

vix install
vix dev

Before commit:

bash
vix fmt --check
vix build
vix check --tests

Before release:

bash
vix build --preset release
vix check --tests

On server:

bash
vix doctor
vix install
vix env check --production
vix build --preset release
vix service init
vix proxy nginx init
vix health

Deploy:

bash
vix deploy --dry-run
vix deploy

Inspect if something fails:

bash
vix logs errors --lines 120
vix service status
vix health

Common mistakes

Running production manually

Wrong:

bash
ssh server
vix run

Correct:

bash
vix service init
vix service restart
vix health

Updating dependencies during deploy setup

Wrong:

bash
vix update

Correct:

bash
vix install

Skipping env checks

Wrong:

bash
vix deploy

Better:

bash
vix env check --production
vix deploy

Checking only local health

Wrong:

bash
vix health local

Better:

bash
vix health local
vix health public

If WebSocket is used:

bash
vix health websocket

Reloading proxy without checking config

Wrong:

bash
sudo systemctl reload nginx

Better:

bash
vix proxy nginx check
vix proxy nginx reload

Cleaning store without preview

Wrong:

bash
vix store gc --project

Correct:

bash
vix store gc --project --dry-run
vix store gc --project

Production checklist

Before first deploy:

txt
Vix installed
vix doctor passes
dependencies installed with vix install
.env exists on server
production.env.required is satisfied
release build passes
tests pass
systemd service exists
Nginx config exists
TLS certificate exists if TLS is enabled
local health passes
public health passes
logs are readable
deploy dry run looks correct

After deploy:

txt
service is active
local health is healthy
public health is healthy
WebSocket health is healthy when enabled
logs do not show startup errors
rollback path is known

What you should remember

Local development is for fast feedback.

Production is for repeatable operation.

The model:

txt
vix dev
  -> vix build
  -> vix check
  -> vix service
  -> vix proxy
  -> vix health
  -> vix logs
  -> vix deploy

Do not treat production as a different universe.

Use the same project.

Make the environment explicit.

Make the service repeatable.

Make the proxy visible.

Make health checks mandatory.

Make logs easy to read.

The core rule:

txt
local should be fast
production should be repeatable
both should be understandable

Next chapter

Next: Next Steps

Released under the MIT License.