Core-only installations

What stays the same and what changes when you upgrade to a release with the plugin host but install no plugin.

You do not need a plugin, a private image, a pull secret, or trust material to run the Platform Orchestrator. Install and upgrade a Core-only installation as described in Self-hosting.

What stays the same

Every plugin setting in the Helm chart is off by default:

ValueDefault
control-plane.configurationProfiles.nativePluginGateway.enabledfalse
control-plane.configurationProfiles.nativePluginEntitlements.enabledfalse
data-plane.configurationProfiles.nativeExternalDeployment.enabledfalse
iam.configurationProfiles.pluginWorkloadIdentity.enabledfalse
iam.configurationProfiles.pluginWorkloadStaticJWKS.enabledfalse
iam.config.PLUGIN_MANAGER_READERSempty
iam.config.PLUGIN_MANAGER_WRITERSempty
console.gatewayApi.route.pluginUiOrigins[]

With these defaults, the chart renders only public Stellwerk and third-party images. Plugin discovery returns an empty list, and every plugin-dependent client command reports plugin_not_installed. Core commands, Core provider resources, and the Console work as before.

A failed ordinary Deployment never blocks the next Deployment of its Environment. Only failed Deployments that a plugin dispatched can block an Environment; see Plugin recovery.

What changes

Concurrent Deployments to one Environment

Deploy and rollback Deployments change their Environment, so they still run one at a time under a per-Environment deployment lease. What changed is what a second request does while the lease is held. The raw API and the standard clients now differ:

CallerRequest while the Environment is busy
Raw API without when_busy, or with when_busy: queueHTTP 202 and a queue item that becomes a Deployment when the Environment is free
Raw API with when_busy: rejectHTTP 409 with the error code environment_busy
Raw API with an organization token, whatever when_busy saysHTTP 409 with the error code environment_busy
octl deploy and octl rollback, the provider, and the ConsoleWait, as the raw API does

Waiting is the default of the API. A client that does not send when_busy, including a script written before the queue existed, now waits where it used to get a 409. Send when_busy: reject (octl deploy --no-queue) to keep the fail fast answer.

Earlier releases answered the busy case with the messages deployment '<id>' is still executing or environment has an active deployment lease. Match the error code environment_busy instead of the text. The lease is released when the running Deployment succeeds or fails. The Data Plane renews it while the Deployment runs, and a lease that is neither released nor renewed expires after 65 minutes.

How the queue behaves

  • Items run first in, first out per Environment, one at a time.
  • A queue item stores your request, not a resolved plan. Core resolves it when it promotes the item, against the Environment as it is then, including Pins and Module Version lifecycle. A queued item therefore never reverts what the item ahead of it deployed, and it can still be rejected later, for example because a Pin was created in the meantime.
  • A failed Deployment does not stop the queue, unless the waiting item asks for it with halt_on_failure; see Stop after a failure. A failed Deployment that a plugin dispatched and whose outcome is unknown does stop it; see Plugin recovery.
  • While an Environment has a queued item, no other path, including a plugin, starts a Deployment there.
  • Plan-only requests and dry runs are never queued; see Plans and dry runs beside the queue. Destroy is never queued; it waits for the running Deployment. Deleting an Environment cancels its queued items.
  • A request that a deprecated organization token authorizes is never queued, because no user remains whose permission Core could check again at promotion. It answers environment_busy when the Environment is busy, whether it omits when_busy or sends queue. Use a user or a service user token to wait.
  • Promotion is safe with several Data Plane replicas and survives a restart without duplicating or losing an item.

A request that was derived from a specific state, such as a merge or a diff you confirmed at a prompt, can name expected_base_deployment_id. If the Environment’s current Deployment differs when the item is promoted, the item is rejected with deployment_base_changed instead of deploying onto a different base. octl sends it automatically for --merge, --drop-workload, --drop-shared, octl score deploy, and every diff that you confirm.

Stop after a failure

By default a queued request starts even when the Deployment ahead of it failed. If the request only makes sense after a successful predecessor, set halt_on_failure: true on it. When Core promotes the item and the Deployment it waited for failed, the item is not started. It ends rejected with the reason code previous_deployment_failed, its reason_message names the failed Deployment, and the queue continues with the next item.

  • The Deployment it waited for is the latest deploy, rollback or destroy of the Environment at promotion, whether it came from the queue, a direct request or a plugin, provided it had not ended yet when the item was queued.
  • A failure that happened before the item was queued does not halt it. A later Deployment that succeeds replaces an earlier failure. Plan runs never count. A Deployment that an operator force-fails counts as a failure.
  • One failure halts every waiting item that set the flag. Items without it start.
  • A request that starts at once has nothing to wait for and never halts.
  • halt_on_failure is refused (400) together with when_busy: reject.

Plans and dry runs beside the queue

Plan-only requests and dry runs change nothing. They are never queued and they ignore when_busy, queue_ttl_seconds and halt_on_failure, so a client can send its usual queue settings with them.

  • A dry run is answered while Deployments run or wait.
  • A plan-only request starts while items wait in the queue, and the head of the queue then waits for the plan run to finish. It does not start while a deploy, rollback or destroy runs, or while another plan run of the Environment runs. It answers HTTP 409 environment_busy, whatever when_busy says, because every Runner step locks the Environment’s Terraform/OpenTofu state and does not wait for the lock.

When Deployments run or wait, the response that creates the plan-only Deployment or answers the dry run carries pending_changes:

"pending_changes": {"code": "result_may_change", "running": 1, "queued": 2}

running is the number of deploy, rollback or destroy Deployments changing the Environment now (0 or 1), and queued the number of requests waiting. The plan or diff describes the Environment as it is now, so it may still change. The field is absent when nothing runs or waits, and later reads of the Deployment do not carry it.

Queue limits

VariableDefaultMeaning
DEPLOYMENT_QUEUE_MAX_DEPTH10Waiting items per Environment. More fail with deployment_queue_full
DEPLOYMENT_QUEUE_DEFAULT_TTL2hHow long an item may wait. It exceeds one lease, so a full-length predecessor does not expire the next item
DEPLOYMENT_QUEUE_MAX_TTL24hUpper bound of a requested TTL. It must not be below the default

Set them on the Data Plane. A client can request a shorter or longer TTL within the maximum with queue_ttl_seconds.

Watch and cancel queued work

octl follows a queued item until it starts, polling every 5 seconds and showing its position and any transient promotion failure. If the item is rejected, expires, or is cancelled, the command fails with the reason code and the next step. Pressing Ctrl+C leaves the item queued and prints the command to cancel it.

CommandPurpose
octl get deployment-queue-items [project] [env] --stateList queue items in order, optionally by state
octl get deployment-queue-item <id>Show one item, including its requested manifest
octl deployments cancel-queued <id> [--reason]Cancel a waiting item
octl deploy --no-queue, octl rollback --no-queueFail at once with environment_busy when the Environment is busy
octl deploy --queue-ttl <duration>Wait for at most this long, in whole seconds of at least 1s
octl deploy --halt-on-failure, octl rollback --halt-on-failureDo not start if the Deployment it waits for fails

--queue-ttl and --halt-on-failure each cannot be combined with --no-queue, --plan-only, or --dry-run. When a plan-only request or a dry run finds Deployments that run or wait, octl prints The result may change: N Deployment running, M queued. They change the environment first. (Deployments when N is not 1). If a request queued with --halt-on-failure is rejected, octl names the failed Deployment and the command to inspect it (octl get deployment <id>). Listing and reading items needs deployment_read on the organization. Anyone with deployment_write on the Environment can cancel a waiting item. The Data Plane records the actor and reason of every cancellation, and records expiry and rejection with their reason.

The API routes are GET /orgs/{orgId}/deployment-queue, GET /orgs/{orgId}/deployment-queue/{queueItemId}, and POST /orgs/{orgId}/deployment-queue/{queueItemId}/actions/cancel. A cancellation that meets a promotion in progress answers HTTP 409 queue_item_dispatching; read the item again. Cancelling an item that has already settled answers queue_item_settled.

Terraform and OpenTofu

The platform-orchestrator_deployment resource accepts when_busy (queue or reject). Unset means queue for mode = deploy, and a plan-only resource never queues. The create and update timeouts (default 20 minutes) cover the request, the queue wait and, with wait_for, completion. If the timeout passes while the item is still queued, the provider cancels it. Changing only when_busy or timeouts updates the state without a new Deployment.

Upgrade note

The queue schema is additive. Its down migration refuses to run while an item is queued, so cancel waiting items or let them settle before you roll back. Clients that never send when_busy now wait for a busy Environment instead of getting a 409, so a script that expects environment_busy must send when_busy: reject. Requests authorized by an organization token still get the 409.

Runner execution budget

The one-hour Runner execution budget now starts at the first durable dispatch attempt, after the Runner bundle is prepared. Time spent waiting for bundle construction no longer counts against a new Deployment. Restarts, broker redelivery, and retries never reset the budget.

Existing Deployments have no recorded first-dispatch time. The upgrade backfills their budget from their original admission time. It does not change their status, history, resources, or outputs.

Extension event journal

The Control Plane keeps an extension event journal only for organizations in which an installed plugin subscribes to resource events. Without the plugin host, or without a subscribing plugin, it writes no journal entries and uses the same transactional outbox path as before. The journal retention job runs only while the plugin host is enabled. There is no separate setting to turn the journal on.

Database schema

The upgrade adds tables to the Control Plane, Data Plane, and IAM databases, even if you never install a plugin. The migrations are additive and leave existing data unchanged. Several down migrations refuse to run once they would discard recorded evidence, such as dispatch attempts or event history.

Back up all three databases before you upgrade. IAM 2.4.0 and earlier refuse to start on the upgraded IAM schema, and older Control Plane and Data Plane images are not qualified against the upgraded schemas. A rollback to older images is therefore not a recovery path. To recover, deploy schema-compatible images of the new release.

New routes and permission

  • The API gateway routes POST /orgs/{orgId}/projects/{projectId}/envs/{envId}/deployments/{deploymentId}/actions/acknowledge-outcome to the Control Plane. Core-only installations never need it, because ordinary Deployments do not block their Environment.
  • The API gateway routes POST /auth/plugin-workload-token, /plugin-workload-bindings, and /platform-principals (list, create, read, delete, and actions/regenerate) to IAM. While workload identity is off, the token exchange answers HTTP 404. Only principals listed in PLUGIN_WORKLOAD_BINDING_WRITERS can manage bindings and platform principals, and the list is empty by default.
  • The API gateway routes the Plugin Manager operator API (/plugin-packages, /plugin-installations, including organization-mode and the organization enablements, /plugin-entitlement-bundles, and /plugin-audit-events) to the Control Plane. With the plugin host off, every route answers HTTP 404 with the code plugin_host_disabled. Only the users that IAM lists in PLUGIN_MANAGER_READERS and PLUGIN_MANAGER_WRITERS can call it, and both lists are empty by default.
  • The IAM permission catalog contains deployment.outcome.acknowledge. The built-in Admin role includes it.
  • IAM also knows the platform permissions plugin_manager_read and plugin_manager_write. Only the configured users hold them, and no organization role grants them. The Console shows its read-only Plugin Manager view only to holders of plugin_manager_read.
  • A Core-only installation has no platform principals and enables no organization for any plugin. The IAM database gains the scope of a service user for platform principals, which changes no organization service user. The Control Plane database gains the tables for several organizations per plugin installation, the organization mode, and installation-wide license grants. Nothing in them applies until you install a plugin.

Clients

The standard octl CLI and Terraform/OpenTofu provider contain commands and resources for Stellwerk Enterprise plugins. Their help text and generated documentation name the required plugin ID. On a Core-only installation they fail with plugin_not_installed before contacting a plugin. See Plugin availability diagnostics.

Top