Deplo

The deploy trace

The full trace of one deploy, from trigger to a container serving traffic.

The full trace of one deploy, from the thing that triggered it to a container serving traffic. Reading it once makes most failure messages self-explanatory.

Something triggers it

TriggerComes from
Deploy or Redeploy in the dashboardA person
A push to the tracked branchThe git provider's webhook
A new tagThe same webhook, when the trigger is set to tags
A POST to the app's deploy hook URLCI, a script, a provider Deplo cannot register a webhook with
A pull request opened or updatedPreviews, if they are on
RollbackA person, and it skips almost all of what follows
The MCP server or the GraphQL APIA token holding deploy_apps

An automatic trigger can be filtered before it starts: watch paths skip the deploy if the push touched nothing under the globs you listed, and skip unchanged deployments skips it if nothing under the root directory changed. A manual redeploy always runs.

It waits for a slot

Every server has a deploy concurrency, 1 by default, so deploys to the same machine queue rather than fighting over CPU and the build cache. Deploys to other servers run in parallel. Two deploys of the same app never overlap.

A queued deploy shows its position in the build log while it waits. If the app builds on a build server, it takes a slot on that machine, because that is where the cost is.

The agent fetches the source

The control plane opens a mutual-TLS connection to the agent on the target server and hands over the job. This is where credentials are decrypted: plaintext exists only inside that call, and the agent never holds the key.

  • Git sources are cloned by the agent, on the host, with the token from the git connection. --recurse-submodules is added if the app asks for it.
  • Upload sources use the archive you uploaded.
  • Docker image and Compose sources skip this step entirely.

The framework is detected

For Nixpacks and Railpack builds, Deplo re-derives the framework from package.json and the root configuration on every deploy. It is never a stored setting.

Detection produces the install, build and start commands and the default container port. Anything you typed by hand wins over it, permanently. Dockerfile and Static builds skip detection: you named everything already.

The image is built

MethodWhat happens
Nixpacks (default)Generates a build plan from the detected language and framework
RailpackThe same idea, a different builder, with its own pinned version
DockerfileYour Dockerfile, with your build context and optionally a --target stage
StaticBuilds, then serves the output directory, with an optional single-page fallback

Every resolved environment variable is available during the build, not just at run time, so build-time inlined configuration such as NEXT_PUBLIC_* works. The generated Dockerfile declares them as ARG and ENV.

The image stays on that host. Deplo pushes to no registry, which is why a rollback needs the image still to be there.

Build output streams to the dashboard as it happens, survives a page reload and survives a control plane restart. It is stored with a cap, so a runaway build cannot fill the disk.

The environment is resolved

Four layers fold together, lowest priority first: instance-wide variables, then the app's own variables, then a shared variable the app has linked, then a preview override (previews only). A name defined on more than one layer takes the value from the highest layer that defines it.

Every layer carries a type. A secret-typed value is write-only, and a preview of a fork never receives one, whatever the settings say.

The stack is rendered

This happens on the control plane, never on the host. It produces:

  • The Compose file. One service for a single-image app, or your authored Compose stack with Deplo's own entries folded in.
  • The Traefik labels. One router per domain, carrying the rule, the entry point, the certificate resolver and the middleware chain. Path routers are given priority above whole-host ones, longest prefix first.
  • The port. One container port per app, plus any per-hostname override.
  • The volumes, the resource limits and the mounts.

The result is opaque YAML by the time it reaches the agent. Compose and routing logic never live on the host side.

The agent brings it up

The agent writes the stack and, when there are secrets, a 0600 environment file beside it, then runs Compose. Containers join the shared deplo network, which is how apps and databases reach each other by name.

Traefik notices the new labels through its read-only socket proxy and starts routing. If the domain asked for a certificate, Traefik requests it on the first HTTPS request.

The result is recorded

The deployment row gets its final status, its image reference, its duration and its logs. The app's status moves to active, or to error if the build or the bring-up failed.

Older images beyond the app's rollbacks kept setting (default 3) become eligible for removal by the server's disk cleanup. That number is the real retention: Deplo pushes to no registry, so a pruned image is gone.


What a deploy does not do

  • It does not touch your data. Volumes are reattached, never recreated.
  • It does not change the slug, the volume names or the app's identity.
  • It does not roll back on failure. A failed deploy leaves the previous stack running. See App status is intent, not observation.

A rollback is much shorter

Steps 3 to 6 are skipped. Deplo re-runs an existing image with the current configuration: the code goes back, the variables, domains, volumes and limits do not. It is recorded as a real deployment of its own, with its own logs.


See also

Did this page help you?

On this page