Deploys & the Staged Pipeline
Every deploy goes through the same four stages:
- Cloning - pulls your branch’s current tip.
- Building - produces a runnable image. If your app has a
Dockerfileat its build path, that’s used directly. Otherwise, Canopy builds it via Cloud Native Buildpacks (the same tool Heroku-style buildpack detection uses) - noDockerfilerequired for common frameworks (Node.js, Python, etc.). - Testing - runs your configured
build_commandorbuild_stepsinside the just-built image. A non-zero exit here stops the deploy before it ever reaches production - the previous release stays up untouched. - 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.
| Status | Meaning |
|---|---|
successful | Built, deployed and healthy. |
failed | A step failed or the pipeline could not complete. |
deploy_failed | CI passed but the deploy did not become healthy. |
timed_out | The pipeline or the deploy did not finish in its time budget. |
errored | An internal Canopy error. |
cancelled / rejected | Stopped 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_timeoutorinternal.kind:user_code,user_config,platform,infrastructure,capacityortimeout.detail: the specific reason, for exampleapp.example.test: hostname already claimedorHTTP 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 dockerfilecanopy environments update <environment_id> --build-type buildpackcanopy environments update <environment_id> --build-type auto # back to the defaultUnder 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 paketocnb_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 falseThis 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.