Platform
Services & instances
What an instance is, how its health is observed, and how to operate it.
An instance is one running service: your NocoBase, your n8n, your own container image. Each instance runs on Canadian infrastructure with its own storage, TLS certificate, and backup schedule. Instances are addressed by slug, unique within your organization.
Health
An agent on each VM reports container state to our control plane: is the service answering, is its database reachable, did setup finish. The dashboard and the API show the same observed state our operators act on.
Health statuses:
- healthy: up and answering.
- unhealthy: the container is running but its own health check is failing.
- degraded: partly up. A container is stopped or crash-looping while another still serves, or setup is still in progress.
- down: no container is serving.
-
unknown: not yet observed, or the last report is stale. Common for a short window right after a deploy. Treat a short
unknownas inconclusive, not as an outage.
Reading and managing instances over the API
-
GET /api/v1/cataloglists what your organization can deploy (read_instances). Each entry carries theslugto pass ascatalog_slug, itsname,category,kind(templateorcustom),default_slug(the conventional instance name),resources(vcpu,mem_mib,disk_gib, its cost against your plan) andrequired_secrets(the deploy-time secrets you must supply, each withkey,label,required,hint). Thecustomentry also carrieslimit_presets, ready-madecustom.limitsvalues (small,medium,large); itsresourcesis the cost of an instance with no limit set. An app you can't deploy self-serve is absent. Not paged. -
GET /api/v1/instanceslists your instances with their health (read_instancesscope). -
GET /api/v1/instances/{slug}returns one (read_instances). -
POST /api/v1/instancesprovisions one from the catalog, or from your own image (below), and returns201with the apply run inmeta.run_id(write_instances). -
PATCH /api/v1/instances/{slug}moves one to a new image and redeploys it; returns202with the run id (write_instances). Instances we manage declaratively on your behalf return422 not_operator_managed. -
PUT /api/v1/instances/{slug}/addressrenames its Lunary address and restarts it on the new name; returns202with the run id (write_instances). See Addresses & custom domains. -
DELETE /api/v1/instances/{slug}tears one down and returns202with the run id (write_instances). The application stops; your data is kept.?remove_volumes=truepurges the instance's data instead of retaining it.
MCP: list_catalog, create_instance and delete_instance (with purge for remove_volumes) take the same fields and return the same codes. See MCP server.
Full parameters and response shapes are in the API reference.
Mirroring another instance
A staging or dev instance can be born as a mirror of one you already run, such as your production NocoBase. In the dashboard's deploy flow, once you pick a non-production environment for an app you already have an instance of, a Sync data from picker offers your other instances of that app. Over the API, pass the source's slug as source on POST /api/v1/instances:
{ "catalog_slug": "nocobase", "slug": "nocobase-staging", "environment": "staging", "source": "nocobase", "sync_cadence": "daily" }
The new instance starts empty, with its sync_db plug enabled and pointed at the source. It also starts with the source's data encryption key (NocoBase APP_KEY, n8n N8N_ENCRYPTION_KEY, Twenty ENCRYPTION_KEY, Docmost APP_SECRET), since the rows a sync copies can only be decrypted with it; its other credentials are its own. Two cases leave the mirror its own key, and its syncs are refused until the keys match: re-created over kept data, it keeps that data's key, and a source whose key we don't store (some instances we set up for you) has none to pass on. sync_cadence (the Refresh picker next to the source) says when it fills: on_demand, the default, creates no schedule, so you run it yourself (POST /api/v1/instances/{slug}/plugs/sync_db/runs, or Sync now on the instance's Settings → Data sync card); hourly seeds a sync_db routine at the instance's own minute past each hour; daily seeds one in the 03:00 UTC band. Either can be adjusted later (PATCH /api/v1/routines/{id}, or the same card). A schedule without a source is refused (invalid_sync_cadence). Each run replaces the destination's database with a fresh copy of the source's; the source is only read. The relationship is reported as source on the instance and as a Data sync row on its Overview.
What a sync copies is the database only. Files the app keeps on disk (uploads, installed plugin bundles, media) are not copied, so a mirror of an app that stores files (NocoBase plugins, for instance) has the source's data but not its files until those are brought over separately. Some rules hold throughout: a production instance can never be a sync destination (invalid_source), the source must be one of your instances running the same app, and an app without a database sync (Ghost, for now) cannot mirror. Instances we manage declaratively on your behalf are refreshed by us, files included, on the schedule agreed with you; they show no Data sync card.
Bring your own app
Your own container image runs as an instance with the same exposure, health, logs, backups and plugs as a catalog app. Deploy it from New deployment → Custom app or from the API. See Custom apps.
Controls
Start, Restart, Pause, Stop and Force stop sit beside Open on the service page. Each is a run of the stack plug with an action (start, restart, pause, stop, kill): one compose command on the containers as deployed, no pull and no re-render. Start unpauses first. A stopped stack stays stopped until started; the page's health notice points at Start meanwhile. Over the API it is POST /api/v1/instances/{slug}/plugs/stack/runs with {"params": {"action": "stop"}}, over MCP trigger_plug.
Updating the image
PATCH /api/v1/instances/{slug} with an image moves the instance to it. The image is validated like custom.image, stored as given, and a redeploy run is created; poll it with GET /api/v1/runs/{run_id}. A custom app's env, secrets, healthcheck, limits and volumes change through the same request, alone or alongside the image; see Custom apps.
- A tag is re-resolved on every redeploy; a digest is not. Pin a digest when you want a deploy to mean exactly one build.
- A custom instance can move to any repository. Port, env, secrets, volumes, and healthcheck are carried over.
-
A catalog instance takes only a new tag or digest of the repository Lunary pins (
repository_mismatchotherwise). Its database and other secondary images stay as pinned. Where the catalog pins a tag variant (Stirling PDF runs-ultra-lite), the new tag must keep that suffix, and a digest is refused because it cannot show one (variant_mismatch). -
The request is refused while a redeploy of the instance is still running (
redeploy_in_flight), while itsredeployplug is switched off (plug_disabled), and when it would move from a registry you hold a credential for to one you do not (no_registry_credential). A move between hosts you hold no credential for is accepted; if the new image is private, the redeploy run fails at pull.
A catalog instance keeps the image it was born with when the catalog pin later moves; nothing redeploys a running instance. GET /api/v1/instances/{slug} and get_instance return the current pin as catalog_image (null for a custom app), so a routine can diff it against image and propose the update. Passing catalog_image as the new image returns the instance to the pin.
The dashboard's Settings → Image card shows the desired image, the image the app container is observed running, what the last successful deploy resolved to (its reference and short digest, with the full digest on hover), and the catalog pin, with a note when the instance runs an older tag. Per-container observed images are in the Overview → Composition card.
Reading with an agent? This page as markdown, or the whole docs as llms.txt.