Preview before production — with a gate where it counts.
GAA deployment ships a specific commit to a named environment. Wire the environment to a provider and TillForge builds and ships it for you — clone, build on the runner, deploy through the provider’s own CLI; or leave it tracking-only and report state back from your own pipeline. Previews land freely; an environment TillForge ships to can wait for approvals from someone else and check the commit against your deploy policy first. Every deploy records the exact commit, so a crash later traces straight back to what shipped.
Define preview and production
An environment is a named target — preview, staging, production, whatever your flow needs. Mark the ones that matter --production and set --approvals so a deploy TillForge runs there waits for sign-off. Preview environments stay open, so a branch or a pull request can go live for review with no ceremony.
# Define your environments. A preview env is open; a production env can
# require approvals from someone else before anything ships.
tilldev forge env set acme-api --name preview --url https://preview.acme.dev
tilldev forge env set acme-api --name production --production \
--url https://acme.dev --approvals 1
tilldev forge env ls acme-apiLet TillForge build and ship it
Wire an environment to a provider and TillForge owns the whole deploy. Connect a provider credential once — a static host (Cloudflare Pages, Netlify, Vercel, Firebase, Surge, Azure Static Web Apps), object storage (AWS S3 + CloudFront), edge functions (Supabase), or a container platform (Railway, Fly.io) — and it’s envelope-encrypted under your org key the moment it arrives; the dashboard only ever shows a hint. Then point an environment at that credential and a build recipe, and every deploy runs on your own CI runner: it clones the exact commit, builds it in the hardened container, and ships the output through the provider’s CLI. The credential is injected into the build only at run time — never stored in the job, never printed in the log.
# 1 · Connect a provider credential once (envelope-encrypted at rest).
tilldev forge provider connect --name "prod cloudflare" --provider cloudflare \
--credential 'api_token=cf_xxx,account_id=abc123'
# 2 · Wire an environment to it + a build recipe, and auto-deploy on push to main.
tilldev forge env set acme-api --name production --production \
--provider cloudflare --credential <credential-id> \
--target 'project=acme-web' --build 'npm run build' --output dist \
--auto-deploy main
# 3 · Deploy — TillForge clones, builds on the runner, and ships via wrangler.
tilldev forge deploy create acme-api --env production --ref main
tilldev forge deploy logs acme-api <id> # the live build + ship log
tilldev forge deploy promote acme-api <id> --env production
tilldev forge deploy rollback acme-api <id> # redeploy a known-good commit
# A rollback to an environment that needs approvals waits for them too.| Provider | Credential | Target |
|---|---|---|
| Cloudflare Pages | api_token (Pages:Edit) · account_id | project |
| Vercel | token | project_id · org_id |
| Netlify | auth_token | site_id |
| Supabase Edge Functions | access_token | project_ref · function (optional) |
| AWS (S3 + CloudFront) | access_key_id · secret_access_key · session_token (optional) | bucket · region · distribution_id (optional) |
| Firebase Hosting | service_account (JSON) | project_id · site (optional) |
| Surge.sh | login · token | domain |
| Azure Static Web Apps | deployment_token | environment (optional) |
| Railway | token | service (optional) |
| Fly.io | api_token | app |
| Desktop bundles | signing: none · pfx · azure_trusted | framework · platforms |
electron-builder cross-builds Linux (.AppImage/.deb) and Windows (.exe/.msi) from one Linux runner; Tauri builds Linux. Point the deploy’s ref at a release tag and the installers attach to that release with published sha256 checksums.none to ship unsigned installers (Windows shows a SmartScreen warning; the sha256checksums still prove integrity). Choose Azure Trusted Signing(azure_trusted, recommended) and the private key never leaves Azure’s HSM — the runner holds only a short-lived token — which is also the only path that earns a new certificate public SmartScreen trust. Or bring your own Authenticode certificate (pfx) for an enterprise or legacy cert you already hold. Linux binaries are never run-signed. The signing secret is envelope-encrypted in the vault and only ever reaches the runner as claim-time environment — never the recipe or the build log.A desktop bundle end to end — connect a signing credential, wire the target (framework · platforms), and cut installers on a release tag:
# 1 · Connect a desktop signing credential (envelope-encrypted at rest).
# Azure Trusted Signing (recommended) — the key never leaves Azure's HSM;
# the runner only ever holds a short-lived token.
tilldev forge provider connect --name "desktop signing" --provider desktop \
--credential 'signing=azure_trusted,tenant_id=…,client_id=…,client_secret=…,\
endpoint=https://eus.codesigning.azure.net,account=my-ts-account,certificate_profile=my-profile'
# …or bring your own Authenticode cert:
# --credential 'signing=pfx,cert_base64=<base64 of your .pfx>,cert_password=…'
# …or skip signing entirely (sha256 checksums still prove integrity):
# --credential 'signing=none'
# 2 · Wire an environment: electron cross-builds Linux + Windows from one runner.
tilldev forge env set acme-app --name desktop --provider desktop \
--credential <credential-id> --target 'framework=electron,platforms=linux,windows'
# 3 · Deploy against a release tag — the installers attach to that release,
# each with a published sha256 checksum. Windows binaries are signed per the
# credential; Linux is never run-signed.
tilldev forge deploy create acme-app --env desktop --ref v1.4.0
tilldev forge deploy logs acme-app <id> # the live build + sign + publish log--auto-deploy main and a push to that branch ships production on its own; mark another environment --auto-preview and every other branch push gets its own preview URL. Push access is the gate, so a fork’s pull request never touches your provider secrets. Watch a build with deploy logs, move a known-good commit forward with promote, or back with rollback.Approvals and the deploy policy
A deploy to an environment with a provider starts waiting. If the environment needs approvals, that many people other than the one who asked must approve it, and on a production environment they must be owners or admins. Each person decides once, one rejection ends it, and a deploy with no decision lapses after 7 days.
Then the workspace deploy policy checks the commit being deployed. Each check is off, warn or block. Warn records a failing result on the deploy and lets it ship. Block holds it: a check still running holds the deploy for up to an hour and then blocks it, and a check that fails blocks it straight away. A deploy that passes ships on its own; nobody has to come back to it.
| Check | Passes when |
|---|---|
supply-0001 | No dependency in the commit’s SBOM has a vulnerability over your dependency policy, apart from accepted exceptions. |
supply-0002 | A secret scan of the commit found no active secret. |
supply-0003 | A code scan of the commit found nothing unaccepted at or above your code bar. |
supply-0004 | A CI build of the commit has signed provenance. |
supply-0005 | A protected branch pointed at the commit at SLSA Source Level 3 or above, with a signed verification summary. |
supply-0006 | An IaC scan of the commit found no Terraform, CloudFormation or Kubernetes finding at or above your IaC bar, apart from accepted ones. |
# A deploy to an environment that needs approvals waits for someone else.
tilldev forge deploy create acme-api --env production --ref main
tilldev forge deploy approvals acme-api <id> # who decided, and each check's result
tilldev forge deploy approve acme-api <id> --comment "checked the diff"
tilldev forge deploy reject acme-api <id> --comment "not during the freeze"
# Which supply-chain checks warn or block a deploy (owners and admins).
tilldev forge deploy policy
tilldev forge deploy policy --rule supply-0002=block --rule supply-0004=block
tilldev forge deploy policy --scope all # every environment, not just productionBy default every check warns, on production environments only. Missing scans of the commit are started when the deploy is created. Rollbacks and promotions are deploys like any other, so they wait for approvals and the policy too. The supply chain page shows which repositories every check covers.
Or track a deploy you ship elsewhere
Leave an environment’s provider unset and it stays a tracking-only record: you deploy a ref — a branch, a tag, or a release — from your own pipeline and report its state back. The natural path is still preview first, then promote the very same commit to production once it’s reviewed. Because a deploy names a commit, a promotion is provably the thing you already saw, not a fresh build that drifted.
# Deploy a ref to an environment. Previews first — a branch or a PR.
tilldev forge deploy create acme-api --env preview --ref feat/checkout \
--url https://checkout.preview.acme.dev
# Promote to production by deploying the same commit there.
tilldev forge deploy create acme-api --env production --ref v1.4.0
# Move a deployment through its lifecycle as your pipeline reports back.
tilldev forge deploy status acme-api <id> --state success
tilldev forge deploy ls acme-api --env productionTrack every deploy
A first-party deploy moves through these states on its own as the build runs; a tracking deploy is moved by your pipeline. Either way the Deploy tab shows the current state of every environment and the full history beneath it.
| State | Meaning |
|---|---|
waiting | Held for approvals, or for a blocking check that is still running. |
blocked | A blocking check of the deploy policy failed, or did not finish within an hour. |
rejected | Someone rejected it. |
queued | Accepted, waiting to start. |
in_progress | Rolling out now. |
success | Live. This is the current deploy for the environment. |
failure | The rollout failed; the previous deploy stays live. |
error | The pipeline errored before it could finish. |
inactive | Superseded by a newer deploy, torn down, cancelled, or lapsed without a decision. |
Each deployment can carry a --url (where the deploy is reachable) and a link to its run. A deployment event fires on every state change, so your own dashboards and chat can follow along.
From deploy to the crash it caused
Because a deployment is pinned to a commit, TillForge can line up commit → deploy → incident. When something breaks after a release, the trail runs from the crash back to the deploy that shipped it and the exact change inside it — the same correlation that powers a pull request’s release view.
Next: Releases & builds for what you promote, or Events & webhooks to wire deploys into your own pipeline. Back to the TillForge overview.