Build And Deploy
Coil expects production teams to treat build, migration, asset publication, runtime startup, and cutover as separate operational steps with explicit verification in between.
That separation is deliberate. A release is not just a compiled binary. It is the coordinated bundle of:
- customer binary
- app manifest
- platform config
- auth package files
- templates and theme assets
- linked customer Rust code
- optional runtime-installed extensions
What Is This?
This page describes the operational release flow for a Coil customer app:
- what to build
- which commands to run
- when to migrate
- when to publish assets
- how to start the runtime
- how to think about production rollout
Why Does Coil Separate These Steps?
Because they fail differently.
- build failures are code or dependency problems
- migration failures are data-plane problems
- asset publication failures are delivery problems
- startup failures are config, dependency, or composition problems
- cutover failures are live traffic problems
If you collapse all of those into one "deploy" button, incident recovery gets much harder.
A Practical Coil Release Sequence
For a serious deployment, use the same high-level order everywhere:
- validate the app and config
- build the customer binary
- run tests and smoke checks
- plan migrations
- apply migrations deliberately
- publish assets deliberately
- start the new runtime
- verify health and critical journeys
- cut traffic over
- keep rollback inputs available until the release is proven stable
Concrete Local And CI Commands
Shoppr exposes the relevant lifecycle from the customer binary:
cd apps/shoppr
./scripts/prepare-local-dev.sh
cargo run -p shoppr -- describe
cargo run -p shoppr -- validate
cargo run -p shoppr -- migrate apply --dry-run
cargo run -p shoppr -- assets publish
cargo run -p shoppr -- up --config platform.dev.toml
Gitly exposes the same shape:
cd apps/gitly
./scripts/prepare-local-dev.sh
cargo run -p gitly -- validate
cargo run -p gitly -- migrate apply --dry-run
cargo run -p gitly -- up
Those commands are not throwaway examples. They are the checked-in operator surface for the demo apps.
Generic Platform Commands
At the platform level, the CLI contract is the same even if the binary name differs:
coil migrate plan --config apps/shoppr/platform.toml
coil migrate apply --config apps/shoppr/platform.toml --dry-run
coil assets publish --config apps/shoppr/platform.toml --dry-run
coil jobs status --config apps/shoppr/platform.toml
coil tls status --config apps/shoppr/platform.toml
Use the customer binary when the app re-exports that control plane. Use coil when you are
operating the generic CLI directly.
What To Build
A production release should version and promote at least:
- the customer binary
- the exact
app.toml - the exact environment-specific
platform.toml - the auth package files
- templates and theme assets
- published asset manifest output when asset publication is enabled
Do not treat the binary alone as "the release."
Migration Execution
Migration execution is a separate step because it changes live state.
Minimum safe pattern:
- run a dry plan
- review executable and manual migration entries
- apply when approved
- record what happened
Shoppr and Gitly both already expose migration reporting from the customer binary. Their validate and migrate output includes the count of migration contracts and any manual customer migration entries.
Important current limitation:
- the repo currently proves the migration reporting surface
- but it does not yet ship a polished public example of a custom customer-owned SQL table migration beyond the built-in reporting path
So document and operate customer-specific schema changes carefully, but do not assume there is a fully finished example app walkthrough for that lane yet.
For more detail, read Database migrations.
Asset Publication
Asset publication is a distinct release step because a healthy binary can still serve the wrong frontend assets if the manifest or CDN state is stale.
Concrete examples:
- Shoppr local dev publishes assets through
cargo run -p shoppr -- assets publish - Shoppr and Gitly both set
cdn_base_urlinplatform.dev.tomlandplatform.toml
Read Asset publication and CDN delivery before treating frontend delivery as part of startup.
Production Deployment Example: Shoppr
A practical Shoppr deployment flow looks like this:
cd apps/shoppr
./scripts/prepare-local-dev.sh
cargo run -p shoppr -- validate
cargo run -p shoppr -- migrate apply --dry-run
cargo run -p shoppr -- assets publish
cargo run -p shoppr -- up --config platform.toml
During local development, the repo-maintainer override path is:
docker compose -f docker-compose.yml -f docker-compose.repo.yml up --build
That override is for working against the monorepo before upstream crates are published. It is not the public deployment model.
Production Deployment Example: Gitly
Gitly is the non-commerce example, but the operational contract is the same:
cd apps/gitly
./scripts/prepare-local-dev.sh
cargo run -p gitly -- validate
cargo run -p gitly -- migrate apply --dry-run
cargo run -p gitly -- up
Gitly is especially useful when you want to confirm that these operational rules are about the platform rather than Shoppr-specific commerce behaviour.
Same-Domain Versus CDN Asset Delivery
Coil supports both same-origin and CDN-style asset delivery, but the choice should be explicit.
Use same-domain delivery when:
- you want the simplest deployment shape
- you do not yet need CDN behaviour
- cache behaviour is easy to reason about without an extra edge layer
Use a CDN when:
- you want aggressive asset caching
- you need better geographic delivery
- you want runtime traffic and asset traffic to scale independently
Concrete examples:
- Shoppr dev:
cdn_base_url = "http://localhost:9000/shoppr" - Gitly dev:
cdn_base_url = "http://localhost:9002/gitly" - Shoppr prod:
cdn_base_url = "https://cdn.example.com"
If the CDN path is used, asset publication must be treated as part of release promotion, not as a background detail.
Common Mistakes
Treating up as the whole release flow
up is runtime startup. It is not a substitute for validation, migration review, and asset
publication.
Applying migrations implicitly during live cutover
Migration execution should be deliberate and recorded.
Publishing assets too late
If the new runtime starts before assets are in place, public pages can break even when the backend is healthy.
Forgetting customer-specific files in the release record
If you cannot answer which manifest, config, and auth package were deployed, rollback confidence will be poor.