TurboPanel Docs
Using TurboPanel

Deploying and running an environment

Deploying is what turns a project's description into running containers on a server. It happens at the environment — the project holds the compose document; the environment is one copy of it, on one server (or a set of servers), with its own hostnames, storage and variables. This chapter covers the whole life of an environment: choosing where it runs, previewing what will be sent, deploying and redeploying, stopping and starting, destroying, reading logs and history, and what every refusal means.

The model

A deploy is a command the control plane sends to the daemon on the target server. The control plane does the thinking — it merges the project compose with the environment's overlay, resolves variables and secrets, schedules services across servers, allocates addresses, and compiles one runtime compose.yaml per participating server. The daemon does the doing — pulls images, checks out and builds source releases, writes the files, and runs Compose. What you see in the app is the command's progress, polled until it reaches a terminal state.

Three consequences shape how the buttons behave:

  • Nothing runs until a server is chosen. An environment resolves its server from its own pin or the project's default; without one, Deploy is disabled and points you at the Hosting tab.
  • Preview is free; Deploy is a command. Preview shows exactly what would be sent without sending it. Deploy enqueues a command that the app tracks to completion.
  • The daemon is the source of truth for status. Container status is what the daemon last reported; the app refreshes it after each tracked command and on Refresh, not on a timer.

Before you begin

  • A connected server in the organization (Servers shows Online).
  • A saved compose document with no blocking lint findings.
  • For any site or Node app in the document, a system user assigned — Deploy stays disabled with No system user — assign one in Bindings until then.
  • Manage rights on the organization.

Placement and scheduling

Open the project's Hosting tab. At Project scope, set Default project server; at an environment's scope, set its Server pin (or clear it to inherit the default).

Back on the environment's Overview, the lifecycle bar now shows Preview ▾ and Deploy instead of No server — set one in Hosting.

The pin is the anchor. Services that declare deploy.replicas or deploy.placement.* are placed by the planner across the organization's connected servers, respecting host-port conflicts, co-location and spread constraints and the per-server replica cap; services on more than one server must share a spanning (driver: overlay) network, which TurboFabric carries. Preview ▾ → Prepared compose shows one block per server whenever the plan spans more than one host.

Any connected server is a valid target, including the host a self-hosted control plane runs on. A one-server installation can deploy to itself.

Preview

Preview ▾ on the lifecycle bar opens Compose Preview.

Merged compose (the default) is the project compose with the environment's overlay applied, plus the live placement shown as x-turbopanel.placement for reading only — the stored document never holds it.

Prepared compose (via the caret) fetches what the daemon would receive: the compiled runtime compose.yaml, the generated .env of non-secret variables, the file paths secrets will be materialized at, and one YAML block per server when the plan spans more than one.

Prepared preview runs the same validation Deploy does, so a refusal shows up here first — same code, same message.

Deploy, redeploy, cacheless

Deploy (an environment with nothing deployed) or Redeploy ▾ (one that has containers) opens Confirm Deployment, showing the prepared compose.

Confirm. The command is enqueued and the bar tracks it — queued, sent, acked, running, then succeeded or failed — and container status refreshes when it lands.

Redeploy ▾ → Cacheless redeploy sends the same command with noCache: true, so image pulls and source builds ignore their caches. Use it when a latest tag moved or a build cache is suspect.

A redeploy with no change is a no-op: the platform hashes the compose document together with each source service's resolved commit, and the daemon skips work whose hash it has already applied. Push a commit or change the document, and the same button does real work.

Health-check gates

Each service carries a Health check policy (its settings page): disabled, warn, or required. At deploy, a service with policy required and no healthcheck in its compose refuses the deploy outright (health_check_missing with required: true). A service with policy warn and no health check stops the deploy once with a prompt naming the services; confirming sends the deploy again with the warning acknowledged.

Deploy strategies

An environment has a deploy strategy, which decides how the new version replaces the old one.

StrategyWhat a deploy doesRolls back on its own?
sequentialStops the old version, runs any pre-deploy migration, starts the new version, then waits for every service to be healthy. This is the default for every new environment.Yes, while it is still safe (see below).
inplaceBrings the new containers up over the old ones and does not wait for health. The strategy an environment created before it existed keeps.No.
bluegreenStarts the new version beside the old one, then switches traffic. Coming in 0.2.x.—

A sequential deploy goes in this order:

  1. Prepare — networks, image pulls and source builds. If this fails, nothing has been stopped and the old version is still serving; the deploy simply fails.
  2. Stop the old version. Services that keep data on a writable volume (databases and the like) stay running so a migration has something to talk to.
  3. Migrate — any pre-deploy command runs. From here on the schema may have changed.
  4. Start the new version, then the health gate: every service must be healthy. A container with a healthcheck must report healthy; one without must stay running for a short stable window; a one-shot service must exit cleanly; a container that exits with an error or keeps restarting fails the gate. The gate gives up after healthTimeoutSeconds.

How a sequential deploy that does not finish ends is shown in Deployment history instead of a plain Failed:

OutcomeMeaningWhat to do
Rolled back (rolled_back)The new version failed before any migration ran, so the previous version was started again and is serving.Fix the cause (the history row gives the reason) and deploy again.
Needs attention (needs_attention)The environment was left stopped on purpose, or the previous version could not be restored. The usual cause: a migration had already run, so the old code is never started on a changed schema.Read the reason, fix forward and deploy again, or restore from a backup. Check the environment before assuming it is serving.

An environment that declares migrations: breaking is treated as past the point of no return from the start, so a failed health gate there is always Needs attention. A first deploy has no previous version to go back to, so a failure is an ordinary failure.

The strategy is set on the environment's options through the API (there is no screen for it yet), next to the compose overlay:

OptionValuesDefault
deployStrategyinplace, sequential, bluegreensequential for a new environment, inplace for an existing one with none set. bluegreen is accepted but runs as sequential until it is built.
migrationsnone, compatible, breaking, unknownunknown. Unknown is never treated as none.
healthTimeoutSeconds10 to 3600120
drainSeconds0 to 360030 (used by bluegreen)
rollbackWindowMinutes0 to 14400 (used by bluegreen)

A project's options may carry the three numbers as defaults for its environments; an environment's own value wins. Send null for a key to clear it. A value out of range, or a key that does not belong (a project cannot set deployStrategy), is refused with deploy_options_invalid and the field is named. Preview (GET /environments/:id/deploy-preview) reports the strategy requested, the one that would really run (effectiveStrategy), the migration status and, when the two differ, the fallbackReasons.

Rolling deploys across servers

An environment whose services land on more than one server (through deploy.replicas or deploy.placement, see Placement and scheduling) is deployed to each of them. A sequential deploy (and bluegreen, which runs as sequential for now) then updates the servers in batches, sized by the Compose deploy.update_config.parallelism of its services:

YAML
services:
  web:
    deploy:
      update_config:
        parallelism: 2   # two servers at a time
SettingEffect
parallelism (whole number, 0 or more)How many servers update at once. Default 1. 0 means every server at once. A negative or fractional value is refused.
Several services set itThe lowest value wins; 0 yields to any other value.
failure_action: pause or rollbackAccepted. Both stop the rollout when a server fails, exactly as the default does. rollback does not undo servers that already updated.
delay, monitor, order, max_failure_ratio, failure_action: continueRefused at deploy with compose_field_unsupported (a warning while you save), so nothing is silently ignored.

Only the first batch is queued. Each next batch is queued when every server in the batch before has applied the deploy. A single-server deploy is unchanged. The deploy response and the preview report rollout as { parallelism, batches }.

When a batch fails. The first server that fails or times out stops the rollout. Servers that have not started are marked failed in Deployment history with the reason rollout stopped … this server was not started, and their commands are cancelled. Servers that were already running finish. Servers that updated in an earlier batch stay on the new version: nothing rolls them back, so the environment can be left on two versions until you fix the cause and deploy again. Each server's own sequential deploy still rolls back to its previous version when it can (see the outcomes above).

An inplace environment ignores parallelism: it updates every server at once and reports parallelism: 0. A compose document that sets it gets a non-blocking warning from Preview and Deploy (deploy.update_config.parallelism is ignored: this environment deploys inplace); set the environment's deployStrategy to sequential to use it.

Coming in 0.2.x

Blue-green deploys, which keep the old version serving until the new one is healthy and then switch, are not built yet. Until they are, asking for bluegreen gives a sequential deploy. The other Swarm update_config settings are refused, as above.

Start, stop, restart, destroy

ActionWhereWhat it doesData volumes
StopLifecycle barStops the environment's containers. Files, images and volumes stay on the server.Kept
StartLifecycle bar (shown when deployed but stopped)Starts the existing containers again. No preview, no rebuild.Kept
RestartAPI (action: restart)Stop then start.Kept
DestroyLifecycle bar, two pressescompose down --volumes: removes the containers, their networks, the site and Node releases, the secrets on disk, the hostnames from the host's Caddy — and the data volumes.Removed

Stop and Start only touch existing containers, so they stay available even when Deploy is disabled for a missing system user. Destroy is the one that loses data; the app asks twice.

Container status and logs

The environment's Overview lists each service's containers with a status badge from the daemon's last report. Refresh asks again.

Logs on a container opens a live tail of its stdout and stderr, streamed from the host on demand. It is never stored: close the panel and it is gone. For the record of what a deploy did, see the next section.

Deployment history

Deployment history on the environment's Overview lists past deploy attempts with their outcome, who or what started them (a person, the system, or Push to <branch> with the short commit when a git push did) and, for each, the transcript the daemon produced — image pulls, builds, the Compose output — kept for 90 days by default (an operator can change TURBOPANEL_EXECUTION_LOG_RETENTION_DAYS).

A sequential deploy also names the engine it ran (Sequential or In place) and, when it did not finish, Rolled back or Needs attention with the reason; see Deploy strategies.

A deploy that never finished is recorded as stalled, with one of two reasons:

OutcomeMeaningSafe to deploy again?
stalled_undeliveredThe daemon never acknowledged the command — it was offline or the control plane restarted before delivery. Nothing happened on the host.Yes.
stalledThe daemon acknowledged the command but never reported an outcome — a restart mid-run on either side. It may still be running on the host.Check the host first.

Reference

POST /environments/:id/deploy

FieldTypeMeaning
acknowledgeHealthCheckWarningsbooleanProceed past the warn health-check prompt. Never bypasses required.
noCachebooleanCacheless redeploy.
strategyinplace | sequentialRun this one deploy with that strategy instead of the environment's. bluegreen is refused with 501 deploy_strategy_unsupported.
migrationstringRefused with 501 deploy_strategy_unsupported for now; declare the migration status on the environment.
refstringRefused with 501 source_ref_unsupported — services deploy their compose-declared branch, and accepting a ref the build would ignore is the one outcome a caller could not detect. Omit it.

Response: the queued command's id and the URL to poll. GET /environments/:id/deploy-preview returns the prepared shape without enqueueing.

POST /environments/:id/lifecycle

{ "action": "start" | "stop" | "restart" }. Operates on existing containers only.

POST /environments/:id/stop

Destroy. Removes containers and volumes, tears down hostnames and releases.

Command statuses

queued → dispatching → sent → acked → running → one of succeeded, failed, timed_out, cancelled. Commands carry a delivery deadline; one the daemon never picks up becomes timed_out and is recorded in history as stalled with the reason above.

The prepared shape

PartContent
composeFiles[]One compose.yaml per participating server, role runtime.
.envNon-secret variables, ${service__KEY} interpolation resolved.
secretPlan[]The file path each secret will be materialized at under /run/turbopanel/deployments/<project>/<environment>/secrets/. Values travel sealed, never in YAML.
servers[]Present only when the plan spans more than one host.

Errors

Every refusal carries a stable error code and, where useful, a message and the offending names. Grouped by what to do about them.

Fix the document

CodeStatusMeaning
compose_empty400The merged document has no services.
compose_merged_invalid422Project and overlay each saved cleanly but their merge is not valid Compose. Open Preview ▾ → Merged compose.
compose_field_unsupported422An unsupported key is present; the message quotes why. Remove it.
compose_field_requires_org_opt_in403A host-level feature — a gated key (privileged, cap_add, use_api_socket, …) or a path outside the service's directory — and the organization has not turned them on. An owner enables them with PUT /organizations/:id/compose-privileged-fields. See Host-level features.
compose_host_access_requires_manager403Host-level features are on, but only an organization manager or owner can deploy them.
compose_host_access_requires_approval403A Git-triggered deploy of host-level content no manager or owner has deployed in this form. Deploy it once from the app.
invalid_deploy_hosting, invalid_deploy_storage400A hosting or storage row attached to the environment is malformed; the message names it.
principal_alias_unknown, principal_required_for_service_kind, site_principal_ambiguous, source_principal_ambiguous, site_cron_unowned, site_managed_directory_unowned422Ownership of a site or Node app is missing or ambiguous — declare x-turbopanel.principal, or assign a system user on Bindings.
source_ref_unresolved422A source service's branch does not exist or the provider refused the lookup.
variable_unresolved, variable_ref_invalid, variable_secret_interpolation422A ${KEY} reference has no value at any scope, is malformed, or interpolates a secret into a plain field.
docker_external_network_unregistered422external: true names a network not registered under Network → Docker networks.
datacenter_ip_required422A hosting row binds to the datacenter scope on a server with no datacenter address.

Fix the placement or the servers

CodeStatusMeaning
server_placement_required409No server resolves for the environment — set the pin or the project default on Hosting. Also the answer when the planner finds no eligible server.
turbofabric_required422Services placed on different servers share a network that is not driver: overlay.
host_port_conflict422Two services on the same server publish the same host port.
constraint_unsatisfiable, colocation_conflict, max_replicas_per_node_exceeded422The planner cannot satisfy deploy.placement or deploy.replicas with the servers available; the message names the constraint.
resource_limit_exceeded409A service asks for more CPU or memory than the organization or server ceiling allows.
fabric_reconcile_pending409TurboFabric is still converging on a server the plan needs; retry shortly.
fabric_reconcile_failed422TurboFabric could not converge; see the server's Network tab.
storage_location_unavailable422A storage entry's primary copy lives on a server other than the one scheduled, and its access mode does not allow that; the message names both servers.

Fix hostnames or TLS

CodeStatusMeaning
hosting_route_conflict409Two hosting rows in the environment claim the same hostname and path.
hosting_hostname_conflict409A hostname declared in compose is already served by another hosting in the organization.
hosting_tls_ref_unresolved, hosting_ip_ref_unresolved422A certificateRef or ipRef names something the organization does not have.
hosting_tls_mode_unsupported422A tls.mode the platform does not implement.
tls_pin_not_found, tls_pin_mismatch, tls_pin_not_ready400The pinned library certificate is gone, does not cover the hostname, or is not ready yet.
acme_requires_public_bind, acme_requires_org_opt_in400Let's Encrypt needs a public bind, and the organization's Let's Encrypt opt-in under Servers → TLS.

Acknowledge and retry

CodeStatusMeaning
health_check_missing409Services with no healthcheck. required: true refuses; required: false is the warn prompt — resend with acknowledgeHealthCheckWarnings: true.
binding_endpoint_unavailable422A bound managed database has no listener endpoint yet; wait for its cluster to finish applying.
source_ref_unsupported501A ref was sent. Omit it.
deploy_strategy_unsupported501strategy: bluegreen or a per-deploy migration was sent. Use inplace or sequential and omit migration.
Edit on GitHub

Last updated on

On this page