Deploy and verify an App

The full deploy-and-verify loop for a revenexx App — validate locally, sign in, deploy with the CLI, and smoke-test against the gateway, plus caching for read-heavy capabilities.

This guide owns the deploy-and-verify loop (the page is deploy-with-the-cli; its title is the outcome): how you get a revenexx App from your laptop to a live capability behind the API gateway with the revenexx CLI, and how you confirm it actually works. The other tutorials link here for flags and checks. It picks up where Build your first App and Add a data model leave off — you have a tested app and want to ship it.

The last section covers a separate job, caching read-heavy capabilities, and it ships in the same deploy. This is for backend developers. You need the CLI installed, your revenexx identity, a tenant slug, and a machine-to-machine X-Revenexx-Api-Key from Cockpit. A development key with full access (*) is simplest for the deploy; the smoke test in step 3 alone needs your App's namespace, locations.* in this example.

Before you deploy: validate locally

A deployment triggers a build. Catch the avoidable failures on your laptop first:

Pre-push checks
node scripts/capabilities.js
revenexx apps generate --schema ./schema.json --manifest ./manifest.json --out ./src
npm test

The first command brings the capability list in sync with your schema and routes, the second regenerates the typed client, and the suite must be green.

Then run the checklist:

  • Route paths are all-lowercase and namespaced under the app prefix.
  • No tenant_id/org_id columns in schema.json.
  • The manifest version is bumped if you changed the schema or capabilities.
  • src/db.generated.js and the capabilities were regenerated after the last change.

1. Sign in

Set your key and tenant once, then sign in. login checks the key against that tenant and saves the key and endpoint, but not the tenant, so keep REVENEXX_TENANT exported (or pass --tenant on each command). Everything runs against the gateway at https://api.revenexx.com.

Sign in
export REVENEXX_API_KEY="rvxk_..."
export REVENEXX_TENANT="<TENANT_SLUG>"
revenexx login --token "$REVENEXX_API_KEY" --tenant "$REVENEXX_TENANT"

The key is your machine-to-machine key, and the tenant is your tenant slug. See Authentication for how the tenant resolves.

To target a self-hosted instance, add --endpoint <url>. There is no project file to create — login, environment variables, and an optional .revenexx.yaml carry your context. See Configuration.

2. Deploy

revenexx deploy app is the whole path in one command. Run it from the app directory — the one with manifest.json. It registers the App if it's new, uploads the code, waits for the platform build, then checks the release round (the review of this upload of the version). Once the release is approved, it publishes the version and installs it on your tenant. If the release is still in review, it stops before publish and install; see When the release waits for approval. The build applies your schema.json (creating or updating tables, scoped to the tenant) and registers the capabilities your manifest declares; the install step is what writes your gateway routes, so a normal deploy has to install.

Deploy
revenexx deploy app

For a new version of an App that already exists, bump the version in manifest.json — app versions are semantic (major.minor.patch) — and run it again.

The flags you'll actually reach for:

  • --function-id <id> — deploy to a specific existing App instead of matching one by name.
  • --no-publish / --no-install — stop before the Marketplace publish or the tenant install. Remember that routes are written at install time, so an uninstalled version answers nothing at the gateway.
  • --timeout <seconds> — how long to wait for the platform build. Default 600.
  • --release-wait <seconds> — how long to wait after the build for the release round to be approved or rejected. Default 120. See When the release waits for approval.
  • --runtime <runtime> (default node-25) and --specification <spec> — used only on a first-time registration.
  • --owner <tenant> — install on a tenant other than the active one. This is different from the global --tenant <slug> (CLI 0.5.0 or later), which sets the tenant the whole command runs as. Ship a schema change uses --tenant to point one deploy at a scratch tenant.

Run revenexx deploy app --help for the live list. deploy comes from a CLI plugin, so it exists only when your CLI is built with it. See Scaffolding.

Don't run revenexx deploy app against an App connected to a Git repository. The command uploads a tarball, and the active deployment loses its commit reference. Git-connected Apps deploy by pushing to the connected branch instead.

List what's registered with revenexx apps list, and review or roll back deployment history with the revenexx apps subcommands (revenexx apps list-deployments, revenexx apps update-deployment). Note there is no apps- prefix on the subcommand — it's revenexx apps create-deployment, not revenexx apps apps-create-deployment.

Schema changes apply additively on each build: a new column or table is added; a renamed column adds the new one without dropping the old. In a release round, the build applies only the part the running version cannot trip over. Dropped columns, a new notNull and constraints on existing rows wait for the approval (see When the version waits for approval). Your authored capability contract is unaffected, so the SDK and gateway surface stay correct — clean up any stale column in a later release.

When the release waits for approval

Upload the codedeploy app · preview appThe platform buildsschema, capabilitiesThe release roundrelease statuswaitingfailedPublish and installroutes are writtenlivein reviewrejectedapprovedresume commandyou: deploy, resumereviewer: approvereviewer: rejectno decision yet

Where the platform reviews releases, a version goes live only once its release round is approved. A version can have several rounds, numbered rc 1, 2, and so on. To find out whether yours does, run revenexx release status in the directory of an App or theme you have deployed before: it lists the rounds when the platform reviews releases, and fails with "this gateway does not offer release rounds yet" when it does not. A deploy shows the same: after the build it prints a release <version> rc <n> (deploy): <state> line each time the round changes state. As of CLI 0.7.5, deploy app and deploy theme wait for that round and say where it stands. deploy site is unchanged. Older CLIs do not wait, and their publish can be refused with HTTP 409. Upgrade the CLI.

After the build, the command reads the release round and waits up to 120 seconds for it to be approved or rejected. Set the wait with --release-wait <seconds>, a positive whole number.

The outcome depends on the round:

  • Approved. The command publishes the version and installs it on your tenant, as described in step 2.
  • Rejected. The command fails with release <version> rc <n> was rejected: <reason> — publish and install did NOT run. It does not publish or install.
  • Still in review, or no round recorded yet. The build is uploaded but not live. The command warns that publish and install did not run, prints the decision due date when the platform reports one, and prints the command to finish later. This is a wait, not a failure.

While the round is open, the safe part of a schema change is already in the tables and tenants still run the previous approved version (unless they opted in to release candidates); the rest lands at approval (see When the version waits for approval).

Finish a deployment that is waiting with its resume command. It skips the upload and picks up the existing build:

Finish after approval
revenexx deploy app --function-id <FUNCTION_ID> --deployment-id <DEPLOYMENT_ID>

For a theme, the resume command takes only the site and the deployment:

Finish a theme after approval
revenexx deploy theme --site-id <SITE_ID> --deployment-id <DEPLOYMENT_ID>

The command an App prints keeps the --owner, --no-publish and --no-install flags you used. The command a theme prints does not: run as printed, it publishes and installs. If the first run had --no-publish, --no-install or --owner, add them to the resume command yourself:

Finish a theme without publishing
revenexx deploy theme --site-id <SITE_ID> --deployment-id <DEPLOYMENT_ID> --no-publish

A preview finishes with the same command under preview.

Check the state at any time with release status. Run it from the app or theme directory, or pass --function-id or --site-id:

Release status
revenexx release status --function-id <FUNCTION_ID>

It lists each round with its version, round number (rc), mode, state, gate results, decision due date and rejection reason.

If the gateway does not offer the release endpoints (it answers 404, 405 or 501), the command runs the flow it used before release approval, and publish and install follow the build. The same happens when the round cannot be read for another reason: the command warns that it continues as before release approval. Publish may then be refused with 409. Without release endpoints, release status and preview accept fail with "this gateway does not offer release rounds yet".

In CI

The wait ends without an error, including when the round is still in review, so do not rely on the exit code there. A rejected round does fail the command. With JSON output (--json), read the result instead: published and installed are false while the release waits, and release.state says where the round stands (release is null when no round is recorded, and also when the gateway has no release endpoints, so read published and installed first).

Previews

To check a build before it goes live, deploy it as a preview:

Preview
revenexx preview app
revenexx preview theme

A preview runs the same flow in preview mode. The CLI describes a preview app as a candidate that is reachable on its own host until it is accepted and approved; a preview theme does not go live until then. The resume command the command prints starts with preview too.

Once you have checked the candidate, give your go with preview accept:

Accept a preview
revenexx preview accept --release-version <VERSION> --rc <N>
  • --release-version defaults to the version in manifest.json or theme.json. Without one, it takes the newest preview candidate.
  • --rc defaults to the version's current candidate round, which the platform picks.
  • --function-id or --site-id skips the lookup by name.

Your go does not replace the reviewer's approval. The command prints the round's state after accepting. If the round is not approved yet, it says the candidate goes live once a reviewer approves it as well. Once the reviewer has approved it, finish with the resume command the preview printed (revenexx preview app --function-id <FUNCTION_ID> --deployment-id <DEPLOYMENT_ID>, or revenexx preview theme --site-id <SITE_ID> --deployment-id <DEPLOYMENT_ID>). That run publishes and installs; preview accept alone does neither.

3. Smoke-test against the gateway

A deployed app answers at https://api.revenexx.com, scoped to your tenant. Test the live capability the same way any partner would call it — this is the real path, with gateway routing and tenant scoping in play, so it catches what a local test can't.

Use the REVENEXX_API_KEY and REVENEXX_TENANT you exported in step 1. Seed defaults, then exercise CRUD and the custom route:

Smoke test
# Seed the main warehouse (idempotent).
curl -s -X POST https://api.revenexx.com/v1/locations/defaults \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" \
  -H "Content-Type: application/json" -d '{}'

# Create a location.
curl -s -X POST https://api.revenexx.com/v1/locations \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" \
  -H "Content-Type: application/json" \
  -d '{"code":"berlin","name":"Berlin store","type":"store"}'

# List locations.
curl -s https://api.revenexx.com/v1/locations \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT"

# Batch availability (the custom route from Add a data model).
curl -s -X POST https://api.revenexx.com/v1/locations/availability \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"sku":"WIDGET-1","quantity":5}]}'

Always send a body, even {}, on a POST to a capability that declares a request schema — the gateway validates the body and rejects an empty one. Confirm your routes are in the per-tenant API surface:

Confirm routes
curl -s https://api.revenexx.com/v1/openapi.json \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" | grep '/v1/locations'

4. Cache the read-heavy capabilities

Reference data — locations, the category tree, market config — is read constantly and changes rarely. The gateway can cache those responses so repeat reads skip the round-trip to your app entirely. You write no caching code: it's a cache block on the capability's define, and the platform does the rest.

Declare the block in scripts/capabilities.js (never by hand-editing manifest.capabilities.json — the next regenerate would wipe it). Attach it to the list and get capabilities for locations:

scripts/capabilities.js
// Tenant-global reference reads: one cached entry shared by every caller,
// dropped automatically when a location changes.
const LOCATIONS_CACHE = {
  enabled: true,
  scope: 'tenant',                 // shared across callers — only for tenant-global data
  ttl: '1800s',                    // a staleness floor if an invalidation is ever missed
  methods: ['GET'],
  vary_by: { query: '*' },         // pagination/filter become part of the cache key
  invalidate_on: ['locations'],    // every entity this read derives from
};

// Attach it to the reads:
//   def('locations.list', 'List stock locations', { method: 'GET', path: '/locations' },
//       { parameters: PAGINATION_PARAMS, response: pageOf(LOCATION), cache: LOCATIONS_CACHE }),
//   def('locations.get', 'Read one location', { method: 'GET', path: '/locations/{id}' },
//       { response: LOCATION, cache: LOCATIONS_CACHE }),

The rules that keep a cache correct:

  • scope: tenant means one shared entry for the whole tenant — use it only for data that's identical for every caller, like reference and config reads. For anything that differs by who's asking, use the default scope: principal (a separate entry per caller) or don't cache it.
  • invalidate_on lists every entity the response reads from. When any of them changes, the gateway drops the cached entries. Miss one and you serve stale data. A write to that entity is what triggers the drop — you never mark mutations as cacheable.
  • Never cache volatile or buyer-specific data on a tenant-shared entry. Stock availability and resolved prices change per request or per buyer; welding them into a cacheable reference read pins the fast thing behind the slow one. Keep them on their own capabilities.

Bump the manifest version and create a new deployment. To confirm it's working, call a cached GET twice — the second response carries X-Cache: HIT:

Verify the cache
curl -si https://api.revenexx.com/v1/locations \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" | grep -i x-cache

Troubleshooting

  • The deployment reports a build failure. The platform puts the validation or schema error in the build output. The usual causes: a capability summary over 255 characters, a route path that isn't all-lowercase, or a dependency key that isn't vendor/app.
  • The warning says the build is not live until it is approved, or that no release round is recorded yet. The round is still in review, or the platform has not recorded it. Check with revenexx release status, then finish with the resume command the warning prints.
  • Publish fails with 409 version_not_approved. The registry refused to publish a version that is not approved, and install did not run. With CLI 0.7.5 or later this happens only when the command could not read the release round and went on as before release approval (it warns "continuing as before release approval"); a round it can read stops the command before publish instead. Older CLIs do not check the round, so they hit it whenever the version is still in review. Check with revenexx release status --function-id <FUNCTION_ID>, then finish with the resume command the error prints once the round is approved. This applies to apps. For a theme, a failed publish or install prints a warning and the command continues.
  • The command fails with "release … was rejected". The reviewer rejected the round; the text after the colon is the reason, and the rejection column of revenexx release status shows it too. Fix that and deploy again; bump the manifest version if the build reports that the version is already registered.
  • The version is already registered. A previous failed build registered the version. Bump the manifest version and create a new deployment.
  • A route is missing from openapi.json. The capability wasn't declared. Re-run node scripts/capabilities.js, confirm the route appears in the list, and deploy again.
  • X-Cache never reads HIT. The capability has no cache block (or it was dropped by a regenerate). Confirm the block lives in capabilities.js, diff manifest.capabilities.json after regenerating, bump the version, and deploy again.
  • A cached response is stale. An invalidate_on entity is missing. List every entity the response derives from, including cross-app dependencies with their {app}. prefix.

Where to go next

Was this page helpful?