Skip to content

Deploys & the Staged Pipeline

Every deploy goes through the same four stages:

  1. Cloning - pulls your branch’s current tip.
  2. Building - produces a runnable image. If your app has a Dockerfile at its build path, that’s used directly. Otherwise, Canopy builds it via Cloud Native Buildpacks (the same tool Heroku-style buildpack detection uses) - no Dockerfile required for common frameworks (Node.js, Python, etc.).
  3. Testing - runs your configured build_command or build_steps inside the just-built image. A non-zero exit here stops the deploy before it ever reaches production - the previous release stays up untouched.
  4. Deploying - zero-downtime container swap, domain/TLS configuration, health checks.

Each stage is tracked independently, so a build page shows exactly which step failed rather than one opaque “build failed.”

When a deploy counts as successful

A build only becomes successful once the deploy is actually live: the new pods are ready, routes and certificates have not been rejected, and the public health check answers. Until then it stays running. Anything that goes wrong after CI passes ends the build instead of leaving it green.

StatusMeaning
successfulBuilt, deployed and healthy.
failedA step failed or the pipeline could not complete.
deploy_failedCI passed but the deploy did not become healthy.
timed_outThe pipeline or the deploy did not finish in its time budget.
erroredAn internal Canopy error.
cancelled / rejectedStopped by a person, or replaced by a newer build.

A failed build carries a failure object (canopy builds show prints it, and so do the API and MCP) saying where it stopped and whose problem it is:

  • stage: source, pipeline, artifact, capacity, admission, release_task, rollout, routing, tls, health_check, deploy_timeout or internal.
  • kind: user_code, user_config, platform, infrastructure, capacity or timeout.
  • detail: the specific reason, for example app.example.test: hostname already claimed or HTTP 502.

A certificate that is still being issued, or a route that is merely pending, does not fail a deploy. Only a definitive failure does. deploy_failed and timed_out builds can be retried like a failed one.

build_command vs build_steps

canopy build-steps add "npm ci"
canopy build-steps add "npm test"
canopy build-steps list --env <environment_id>

Multiple build_steps run in order inside one shared container (so npm ci’s installed dependencies are visible to a later npm test step). If you don’t need multiple ordered steps, a single --build-command on environments update works the same way for one command.

Monorepos and build_path

Set --build-path (on environments create/environments update) to build from a subdirectory instead of the repo root - this is also how multi-service environments let one repo’s frontend and backend deploy independently.

Build methods: Dockerfile vs. buildpacks

By default (build_type: auto), Canopy checks for a Dockerfile at your build_path first. If one exists, it’s built directly. Otherwise, Canopy falls back to Cloud Native Buildpacks - no Dockerfile required for common frameworks (Node.js, Python, Go, Ruby, Java, PHP, .NET, and more, via each buildpack’s own detection). Framework suggestions in the dashboard recognise the same set, plus Angular.

Set build_type explicitly if you’d rather not rely on that fallback:

canopy environments update <environment_id> --build-type dockerfile
canopy environments update <environment_id> --build-type buildpack
canopy environments update <environment_id> --build-type auto # back to the default

Under dockerfile, a missing Dockerfile fails the build outright instead of silently switching to buildpacks - useful if you want a missing Dockerfile to be a loud error, not a surprise builder switch.

cnb_builder and cnb_buildpacks

When Canopy builds via buildpacks, it uses one of two Canopy-curated CNB builder images - never an arbitrary, customer-supplied builder:

  • heroku (default) - Heroku’s own maintained builder, broad language/framework coverage.
  • paketo - a Paketo Buildpacks builder, useful for stacks Heroku’s builder doesn’t cover as well (e.g. some JVM/.NET setups).
canopy environments update <environment_id> --cnb-builder paketo

cnb_buildpacks lets you pin an explicit, ordered list of buildpacks instead of relying on the builder’s own auto-detection - useful when detection picks the wrong buildpack for a polyglot repo, or you want to add a buildpack the default detection group doesn’t include (e.g. a team-registered third-party buildpack, once your team has one registered - see the Canopy Runner docs for the broader team-authored-buildpack story). Passing a non-empty list replaces the builder’s default detection group entirely - it doesn’t append to it - so include everything your build actually needs:

canopy environments update <environment_id> \
--cnb-buildpacks "heroku/nodejs,heroku/procfile"

Leave it empty (the default) to use cnb_builder’s own default detection order - the common case, and what “auto-detects your framework” means day to day.

build_cache_enabled

Buildpack builds cache layers between builds by default (build_cache_enabled: true) so an unchanged dependency lockfile doesn’t get reinstalled on every deploy. Disable it if you’re debugging a build that seems to be serving stale cached layers, or want every deploy to build fully from scratch:

canopy environments update <environment_id> --build-cache-enabled false

This only affects buildpack builds - a Dockerfile build’s caching is whatever your Dockerfile/Docker’s own layer cache already does.

Checking an environment’s current build configuration

canopy environments show <environment_id>

prints build_type, cnb_builder, cnb_buildpacks, and build_cache_enabled alongside the rest of the environment’s build configuration.

Manual deploy approval

An environment can require a human to approve a deploy after clone/build/test finish, before it actually goes live - useful for production environments where you want a final check. The build stays in an “awaiting approval” state until approved or rejected from the dashboard.