Guides

Building and deploying

What happens between pushing a commit and serving it, and every lever you have over that process.

What triggers a build

  • A push to the tracked branch, when automatic deploys are on (the default).
  • The console — the Deploy button on the app page.
  • The CLI — codaracloud deploy <app> --source.
  • The API, for CI systems and automation.

Turn automatic deploys off per app if you would rather release manually. The setting lives on the app’s settings page, or use --manual-deploys when creating from the CLI.

Watch paths

In a monorepo, watch paths stop unrelated changes from rebuilding everything. They are glob patterns relative to the repository root, and a push that matches none of them is recorded as a skipped deploy rather than being silently dropped.

bash
$ codaracloud apps update api \    --client my-workspace \    --watch-path "services/api/**" \    --watch-path "packages/shared/**"

Watch paths fail open

If the set of changed files cannot be determined for a push, the build runs. Codara would rather deploy unnecessarily than miss a release.

One repository, several apps

The same repository and branch can back several apps across different workspaces, projects and environments. A single push enqueues a build for every app that matches — which is exactly how you keep staging and production on the same branch, and why watch paths matter in a monorepo.

How your image is built

Codara inspects the build context and picks a strategy in a fixed order. The first match wins:

OrderConditionStrategy
1A file named exactly Dockerfile at the build root, or an explicit Dockerfile pathYour Dockerfile
2nixpacks.toml at the build rootNixpacks
3A recognised language manifestRailpack (the default buildpack)
4Static assets onlyA generated nginx image
5Nothing recognisableThe build fails with a detection error

Dockerfile detection is case-sensitive

Automatic detection matches the exact filename Dockerfile at the root of the build context. A lowercase dockerfile, or one in a subdirectory, will not be picked up — your repo will fall through to a buildpack instead. Set the Dockerfile path explicitly if yours lives anywhere else.

Nixpacks is opt-in, not the default

A Node, Python or Go repository with no nixpacks.toml is built by Railpack, not Nixpacks. Add the file only if you specifically want Nixpacks.

Root directory

Set a root directory to build from a subdirectory. Everything — Dockerfile detection, buildpack detection, the build context — is resolved relative to it.

Build arguments versus runtime variables

These are two separate channels and mixing them up is a common source of confusion.

Build argumentsEnvironment variables
Available during buildOnly to your own Dockerfile, through its ARG instructionsNo
Available at runtimeNoYes
Suitable for secretsNeverYes
LimitsUp to 32 values, 1024 characters in totalUp to 65536 characters per value

Never put a secret in a build argument

Build arguments are validated as non-secret and are baked into image layers. Anything sensitive belongs in environment variables, which the build cannot see.

Build arguments reach a build only through a Dockerfile you wrote: each one fills the ARG instruction with the same name. Builds without your own Dockerfile (Railpack, Nixpacks and static sites) do not receive them today, even though you can still set them on the app.

If your framework needs a public value at build time, such as an API base URL baked into a client bundle, you have two options:

  • Commit it to the file your framework reads at build time. A Next.js app reads NEXT_PUBLIC_ values from a committed .env.production, and that works with Railpack.
  • Build with your own Dockerfile, declare ARG NAME (and ENV NAME=$NAME if the build tool reads it from the environment), and set the value as a build argument.

Either way the value ends up in the image or the client bundle, so use this only for values you would publish. Anything else belongs in environment variables.

The rollout

A finished build does not mean a finished deploy. Codara pushes the image, then rolls it out and waits for the new pods to actually become healthy before marking the release live.

By default the rollout is gradual: new pods must come up healthy before old ones are retired, so there is no downtime. An app with a volume attached uses a stop-then-start rollout instead, because the disk can only be mounted by one pod at a time — expect a short gap on every deploy for those.

A failed replacement preserves the healthy release

If the new version never becomes healthy, the deploy is marked failed and the previous release keeps serving. You do not need to intervene to stay up — only to find out why the new one failed.

Health checks

Configure a health check path on the app and Codara uses it to decide both whether a pod is alive and whether it is ready for traffic. Without one, a container that starts but never becomes usable can still be rolled out. A cheap /health endpoint that returns 200 is worth adding.

Pre-deploy commands

A pre-deploy command runs once in the new image before any traffic moves to it. This is where database migrations belong: if the command fails, the release is abandoned and your current version keeps serving.

npm run migrate:deploy

Rolling back

Rollback re-releases an earlier deploy’s image. It does not rebuild, so it is fast and produces exactly the bytes that were running before.

bash
$ codaracloud deploys list my-api$ codaracloud rollback my-api --to-deploy <deploy-id>

The rollback is recorded as a new deploy pointing at the old image, so your history stays honest about what was running when.

Some older deploys need a rebuild

You can roll back to any of an app’s 10 most recent successful deploys, the live one included: Codara keeps their images. Images from before 24 September 2026, other than those apps were running, were not copied to Codara’s EU registry, and the GitHub copies were deleted that day. The console shows which deploys can be restored. To go back further, or to one of those deploys, build its commit again with codaracloud deploy my-api --commit <sha>.

Promoting between environments

Promotion moves the exact image you tested in one environment into another, with no rebuild. The target keeps its own environment variables, domains and scaling — only the image changes.

bash
$ codaracloud promote my-api \    --from-deploy <deploy-id> \    --from-env staging \    --to-env production

See Environments for how to structure this.

Cancelling a build

A queued or in-flight build can be cancelled up until the point it is promoted. The currently serving release is unaffected.

bash
$ codaracloud deploys cancel my-api <deploy-id> --confirm

Deploying a prebuilt image

An app can point at an existing container image instead of a repository, which suits third-party software and images built elsewhere.

bash
$ codaracloud apps image my-app ghcr.io/acme/my-app:v1.4.2

Passing the same reference again re-pulls it, which is how you refresh a floating tag like :latest. For a private registry, configure credentials first:

bash
$ echo "$REGISTRY_TOKEN" | codaracloud apps registry set my-app \    --username acme-ci \    --password-stdin

The password is only ever read from stdin, and credentials are never returned by the API.

Build limits worth knowing

LimitValue
Maximum build duration30 minutes, after which the build is failed
Concurrent builds4 at a time across the platform — further builds queue and report their position. Each running build also reserves 2.5 vCPU and 4.5 GiB of your plan’s hard ceiling for apps and builds.
Build log retention24 hours. After that the logs endpoint returns empty.
Build retriesNone — a failed build is final until you trigger another

Build logs are not archived

Build output is read live from the build container and disappears about 24 hours after the build finishes. If a build failure matters, capture the output while it is still there.

Plan limits can block deploys

Each plan includes a number of build starts per calendar month (UTC), and failed builds count. Once they are used up, new builds cannot start until next month; running apps are unaffected. Other requests past a plan limit are refused, usually with 402 plan_limit_exceeded, which names the limit. A cancelled or expired subscription allows no new deploys, and its running workloads are paused; data is kept. See Plans and capacity.

Last updated