Running a deployment

Install the stack, activate it with your key, and keep it current. A fully hosted tenant arrives with all of this done, so this page is for operators running Veblen on their own server.

1. Install

One command clones the deploy shell to /opt/veblen, asks for your domain, admin account, and registry token, writes .env, and starts the stack.

bash <(curl -fsSL https://git.sotekzone.com/judah/veblen-public/-/raw/main/contrib/install.sh)

To do it by hand, clone the same repository, copy .env.example to .env and fill it, then authenticate to the admin registry with the deploy token this portal issued for your deployment.

docker login registry.sotekzone.com -u <token-name> -p <token>
docker compose --profile production pull
docker compose --profile production up -d

The production profile runs the app image behind nginx, alongside Postgres and Letta. VEBLEN_VERSION pins the image tag, and an empty value tracks latest. Set COMPOSE_PROFILES=production in .env to drop the flag from every command.

2. Activate

A deployment serves nothing until it holds a valid license grant. Until then every route redirects to /setup. There are three ways past it.

PathHowWhen to use it
Setup wizardOpen /setup and paste the license keyThe normal first boot
HeadlessSet LICENSE_KEY and VEBLEN_DEPLOYMENT_ID in .envScripted or rebuilt deployments
Offline grantPaste a signed grant issued by the adminAn install that cannot reach this portal

Both values appear in your dashboard when the deployment is created. Activation stores the signed grant, and the asset classes named in it are the ones the deployment can enable.

3. Credentials

CredentialPurposeIssued by
LICENSE_KEYEntitlement, tier, and asset classesThis portal, per deployment
VEBLEN_DEPLOYMENT_IDBinds the key to this deploymentThis portal, shown with the key
Registry deploy tokendocker pull the Veblen imageThis portal, backed by a GitLab deploy token
RECONCILE_TOKENLocal cron to the deployment's own reconcilerYou, with openssl rand -hex 32

Everything else in .env is yours, the Stripe and PayPal keys that collect your money, the SMTP mailbox that sends your mail, and the market-data keys you hold accounts for. None of it reaches this portal.

4. How the license stays current

A running deployment re-verifies once a day and caches the signed answer it gets back. It keeps honoring a cached grant for 14 days past expiry, so a portal outage, a lapsed renewal, or a weekend with no network never interrupts an appraisal in progress.

When verification finally fails, the deployment locks to its /setup screen. Nothing is deleted and the database is untouched, so activating a valid key brings the practice back exactly as it was.

5. Verifying a license

POST https://veblen.dev/api/license/verify
Content-Type: application/json

{
  "license_key": "vbl_...",
  "deployment_id": "...",
  "version": "v20.4.1"
}

The response carries the decision and a signed JWT.

{
  "valid": true,
  "reason": null,
  "tier": "revenue_share",
  "mau_cap": 0,
  "asset_classes": ["vehicle", "personal_property"],
  "expires_at": null,
  "token": "eyJhbGciOiJFUzI1NiIs...",
  "cache_seconds": 86400
}

Verify the token against /.well-known/jwks.json and cache it. A zero mau_cap means uncapped, which is what both pricing models issue, since users are never metered. asset_classes lists the classes the deployment may enable, and the same list is signed into the token, so editing the database cannot widen it.

6. Heartbeat

POST https://veblen.dev/api/heartbeat
Content-Type: application/json

{
  "license_key": "vbl_...",
  "version": "v20.4.1",
  "schema_tip": "0042_add_testimonials",
  "mau_count": 18,
  "ledger_balanced": true
}

Sent hourly by the deployment. The response echoes the license status and whether the reported user count is over cap. Your dashboard marks a deployment stale after three silent days, which is the signal that a server went down rather than a license going bad.

7. Denial reasons

ReasonMeaning
license_revokedKey revoked, or not recognized
license_suspendedSuspended in the portal
license_expiredPast its expiry date
subscription_canceledThe account's subscription ended
subscription_unpaidDunning exhausted without payment
deployment_mismatchThe key belongs to a different deployment

A past_due subscription still verifies. Suspension is an explicit step once dunning is exhausted, so one failed card charge does not take a practice offline.

8. Upgrades and backups

Upgrading is a pull and a recreate. The app lands any new schema modules itself on first boot, so there is no migration step to run.

docker compose --profile production pull
docker compose --profile production up -d --remove-orphans

Back up Postgres and Letta together with the script in the deploy shell. It reads the database name and user out of your .env and keeps seven days.

contrib/backup/pg_backup_all.sh /opt/veblen /opt/backups/pgdb

Run it from host cron rather than inside the app, so it keeps working on the days the stack is down, which are the days a backup matters most.

0 2 * * * /opt/veblen/contrib/backup/pg_backup_all.sh /opt/veblen >> /var/log/pg_backup.log 2>&1