Publish and install
revenexx deploy app publishes and installs by default, and it looks redundant to publish and install a version of an app the tenant already has. It is not.
Routes are written at install time
A new capability, a changed route, a new schedule — none of them are reachable until the install step runs for that version.
That is the fact behind the most common "my app deployed but the endpoint 404s" report. A build that succeeded proves your declarations were accepted; it does not put anything on the gateway. The install is what writes your routes for that tenant.
So --no-install leaves you with a built, published version that answers nothing new. It is useful for exactly one thing — separating "does this build" from "does this go live" in a pipeline — and confusing for everything else.
The same is true of a schedule: a tick starts firing for a tenant when the version declaring it is installed there.
Publish
Publishing makes a version available to be installed. For a public app it is what puts the version in the Marketplace; for a private app it is the same step with an audience of one.
revenexx deploy app # builds, publishes, installs
revenexx deploy app --no-publish # builds only
Unpublishing removes a version from the Marketplace so nobody new can install it. It does not uninstall it from the tenants that already have it, and it does not stop their traffic. Treat it as "stop offering this", not "recall this".
That distinction matters when you find a bad version: unpublishing stops the bleeding for new installs, and fixing the tenants already on it means shipping a new version and installing it.
Install
Installing puts a published version onto one tenant. Three things happen that are worth knowing individually:
- The routes are written. Your capabilities become live paths under
/v1for that tenant, and appear in that tenant's/v1/openapi.json. - The grants take effect. The access register is enforced from here on, and the merchant has consented to it.
- The dependencies are resolved. A
dependenciesentry is installed automatically; apeerDependenciesentry becomes a prompt. See Dependencies.
revenexx deploy app --owner acme-staging
A Marketplace install is different in one way that bites
When a merchant installs your app from the Marketplace, everything above still happens. What you cannot rely on is the event:
app.installed does not reliably fire on a Marketplace install.
So an app that seeds its defaults only in an install-event handler ships to a merchant with an empty configuration and no obvious reason why. The reliable pattern, which every shipped app uses:
- Expose an idempotent
/defaultscapability. - Seed on the
app.installedevent as well. - Call
/defaultsexplicitly once after install.
curl -X POST https://api.revenexx.com/v1/serials/defaults \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{}'
It is not a workaround for a bug you can wait out — it is the reliable path. See Verifying live and Events you receive.
Make the seed idempotent by checking before you write. A /defaults that duplicates rows on a second call is worse than no /defaults, because both the event and the explicit call may land.
Uninstall
A tenant can uninstall your app. app.uninstalled is delivered to apps that listen for it, and it is the one chance you get to do anything.
What to do in that handler is usually nothing destructive. A merchant uninstalling and reinstalling should not lose their data, and an uninstall is not a deletion request. Cancel outbound work, stop expecting to be called, and leave the rows alone.
Upgrades
Whether and when an installed tenant moves from one version of your app to the next is an operator and Cockpit concern, and the rules are not something we can state accurately today.
Do not design around an assumed auto-upgrade window, and do not assume a tenant will be on your latest version.
What you can rely on is the contract discipline. Because a breaking change is always a new capability key on a new route, a tenant on an older version keeps working whether or not anybody upgrades them. That is the property to design for:
- Never remove a capability a tenant might still be calling. Add the replacement; leave the original serving.
- Never assume a caller has your latest contract. Additive changes are safe precisely because old callers ignore what they do not know.
- Do not rely on every tenant getting a fix at the same time. If a fix matters, deploy it per tenant and confirm it.
Next steps
- Deploying — the command that runs all of this.
- Verifying live — proving the install worked.
- Consent — what the merchant agrees to at install.
- Versioning — why an old tenant keeps working.