Heximon Logo
CLI

Deploying a Heximon app

heximon deploy runs vp build then ships via wrangler deploy, vercel deploy --prebuilt, netlify deploy, or deployctl deploy — with --provision, --create, --migrate, --profile, --verify, and --destroy.

heximon deploy builds your app and ships the artifact to whatever platform your heximon.config.ts declares — the same vp build you'd run by hand, then a handoff to that platform's own CLI. It never reimplements the vendor's upload logic; it resolves credentials, runs the build, and shells wrangler deploy, vercel deploy --prebuilt, netlify deploy, or deployctl deploy for you.

terminal
pnpx heximon deploy

What it ships to

The platform comes from your config's platform value — the same instance that shapes the build already carries its own ship logic:

PlatformShips withCredentials
Node (default, no platform declared)Reports the node dist/server.js run path — no vendor uploadnone
Cloudflare Workerswrangler deploy --config dist/wrangler.jsonnone required locally (wrangler login); CLOUDFLARE_API_TOKEN for CI
Vercelvercel deploy --prebuilt (--prod for the production environment)none required locally (vercel login); VERCEL_TOKEN + VERCEL_ORG_ID + VERCEL_PROJECT_ID for CI
Netlifynetlify deploy (--prod for the production environment)none required locally (netlify login); NETLIFY_AUTH_TOKEN + NETLIFY_SITE_ID for CI
Deno Deploydeployctl deploy <entrypoint> (--prod for the production environment)none required locally (deployctl login); DENO_DEPLOY_TOKEN for CI

A production environment ships with the vendor's --prod flag; anything else (staging, preview:<id>) ships a preview. Override the resolved platform with --platform cloudflare when your config doesn't declare one — useful for a project that sets platform in its Vite config instead of the heximon.config.ts config file this command reads.

terminal
pnpx heximon deploy --env staging
pnpx heximon deploy --platform cloudflare
AWS Lambda has no ship command here. Lambda has no prebuilt one-shot CLI deploy — it's provision-first (function + API Gateway/Function URL + IAM), and a code update needs aws lambda update-function-code against an already-existing function. heximon deploy fails loud on a Lambda-configured project rather than shipping an unverified command.

Before anything ships: preflight

On a real run (not --dry-run), the command first checks that the platform's own CLI is installed and authenticated — wrangler whoami / vercel whoami / netlify api getCurrentUser — before touching the build or any side-effecting step. A missing binary halts with install guidance; a logged-out CLI halts with the login command. Nothing runs half-finished because the vendor CLI turned out not to be logged in.

Bootstrap a fresh project — --create

On a brand-new Vercel or Netlify account, the project or site doesn't exist yet — deploy --prebuilt and netlify deploy both need something to link to. --create <name> creates it and links your directory to it, before the build:

terminal
pnpx heximon deploy --create my-app-production --team my-team

It's idempotent — an existing target with that exact name is linked, never re-created — and it classifies by exact name match rather than trusting the vendor CLI's own exit code (Vercel's link --project auto-creates a missing project and exits 0; Netlify's sites:create --name auto-renames on a collision and also exits 0).

--team scopes the bootstrap to a multi-team account and only makes sense together with --create. Cloudflare needs no bootstrap — wrangler deploy creates the Worker by name on first ship — so --create there is a reported no-op.

Apply pending migrations with --migrate

A schema change on a filesystem-less edge target can't migrate itself at boot, so --migrate applies every auto-detected database's pending migrations through its local drizzle-kit, sequenced around the ship:

terminal
pnpx heximon deploy --migrate --env-file .env.production

Detection reuses the same DatabaseConfigDetector as heximon migrations — every config it classifies as plain drizzle-kit-applicable becomes a migration target; a Cloudflare D1 config (wrangler-d1) or a Durable Object SQLite config (skip) becomes an informational note instead, since neither applies through drizzle-kit.

--env-file supplies the connection env the spawned drizzle-kit child needs — the deploy never loads your dev .env cascade for this (a stray dev DATABASE_URL would silently migrate the wrong database during a production deploy), so the child inherits only this process's env overlaid by --env-file.

A pending batch that looks destructive (a DROP, a narrowing, a new NOT NULL) needs an explicit --allow-destructive acknowledgment — without it the deploy halts rather than apply a drop alongside code that stops using the column, which would break a safe rollback:

terminal
pnpx heximon deploy --migrate --allow-destructive --env-file .env.production

A bare --migrations <dir> (no --migrate) is the read-only sibling: it gates the deploy on pending migrations existing in that directory without applying anything.

Create the resources the vendor CLI won't — --provision

Some infrastructure the vendor's deploy command doesn't auto-create — Cloudflare Queues, for one. --provision creates it after the build (so a strategy can read the emitted deploy config) and before the ship (so the code that binds it finds it already there):

terminal
pnpx heximon deploy --provision --env-file .env.production

--env-file here pushes your app's runtime secretsDATABASE_URL, API keys, CRON_SECRET — into the target platform's native secret store (wrangler secret put, vercel env add, netlify env:import), never as a command-line argument. --env-file needs --provision or --migrate to consume it; passing it alone is rejected, since otherwise the values would silently go nowhere.

Deploy with a named wrangler profile — --profile

Cloudflare-only: ships (and every provisioning/secret call in the same run) as a named wrangler auth profile rather than the ambient login.

terminal
pnpx heximon deploy --profile my-client-account

Any other resolved platform rejects --profile up front. A CLOUDFLARE_API_TOKEN in the environment still takes precedence over the profile, matching wrangler's own precedence.

Confirm the app actually came up — --verify

A vendor CLI exiting 0 means the artifact uploaded, not that the app booted. --verify GETs the deployed URL after a real ship and asserts a 2xx, retrying briefly to absorb cold-start latency:

terminal
pnpx heximon deploy --verify

There's no assumed /health route — the framework mounts none by default — so this checks root-URL liveness only. A boot-crashed deploy (5xx, or nothing answering) becomes a loud, non-zero exit instead of a silent success.

Preview without shipping — --dry-run

Runs the build and every resolution step, and reports exactly what would ship — no vendor upload, no secret push, no resource creation:

terminal
pnpx heximon deploy --dry-run --provision --migrate

Combine it with --provision / --migrate / --create to preview the whole plan for a change you haven't run yet.

Tear it down — --destroy

The inverse of a ship — currently wired only for Cloudflare (wrangler delete, reading the committed wrangler.{jsonc,json,toml} for the Worker's name; no build needed). A platform with nothing to remove (Node) reports unsupported.

terminal
pnpx heximon deploy --destroy

Interactively this asks for confirmation first; in a non-interactive shell it refuses to run without --yes, since a destructive teardown should never fire from an unattended script by accident.

What it detects along the way

Before shipping, the command scans your app's imports for classes that declare themselves DeployableService — an Upstash Redis client, a Turso/libsql connection, a Resend transport, and so on — and reports what each needs:

heximon deploy
Detected 2 external service(s) from your imports:
· upstash (redis-kv) — needs UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN (not set here)
· resend (email) — needs RESEND_API_KEY
Ensure each is configured in your platform's secret store before deploying.

This is purely informational — no central registry to maintain, and no annotation for you to write. A vendor class declares its own requirements once; the CLI extends automatically as your imports change.

The foundational-id verb — heximon provision

Some resources return an id the compiler needs at build time — a Cloudflare Hyperdrive binding with an empty id fails the build outright, but provisioning is what creates that id. That chicken-and-egg can't ride the post-build --provision flag above, so it has its own pre-build command:

terminal
pnpx heximon provision --save-ids

It reads the resources your config declares (not the built artifact — there's nothing built yet), creates the missing ones via the vendor's REST API, and reports each binding → id. --save-ids records them in a per-environment ledger (.heximon/deploy-env.<env>.json, non-secret ids only) so heximon deploy injects them into the next build automatically — the one-time flow is heximon provision once, then heximon deploy from then on.

--profile isn't accepted here: provisioning talks to the Cloudflare REST API with CLOUDFLARE_API_TOKEN, which a wrangler auth profile doesn't carry.

From zero to deployed on Cloudflare

Putting the pieces above together, end to end, for a fresh Cloudflare project with a Hyperdrive binding in its config (an id --provision alone can't create — the build needs it up front):

terminal
pnpx heximon provision --save-ids

This reads heximon.config.ts (no build yet — there's nothing built), creates the missing Hyperdrive binding via the Cloudflare REST API, and writes its id to .heximon/deploy-env.production.json — a per-environment, non-secret ledger. Expect output shaped like:

heximon provision
┌  heximon provision · production
│
◇  Platform: cloudflare · target class: edge
│
◇  Provisioning foundational resources…
│
◇  Provisioned hyperdrive-binding → id 3f1a9c2e-....
│
◇  Recorded HEXIMON_HYPERDRIVE_ID_HYPERDRIVE_BINDING in .heximon/deploy-env.production.json — `heximon deploy` injects it into the build.
│
└  Provisioning complete — ids recorded in the deploy ledger; deploy when ready.

Run it once per environment. From here on, heximon deploy reads the ledger automatically — the id never needs a manual paste into wrangler.json or an env var.

terminal
pnpx heximon deploy --env-file .env.production --migrate --verify

--env-file pushes your app's runtime secrets to wrangler secret put; --migrate applies pending Drizzle migrations against every auto-detected database before the ship; --verify confirms the worker actually answers after wrangler deploy exits. A representative run:

heximon deploy
┌  heximon deploy · production
│
◇  Platform: cloudflare · target class: edge
│
◇  Resolved 1 credential(s).
│
◇  Loaded 4 secret(s) from .env.production.
│
◇  Injecting 1 deploy-ledger value(s) into the build: HEXIMON_HYPERDRIVE_ID_HYPERDRIVE_BINDING.
│
◇  Applying migrations for 1 database(s): app.
│
◇  Deploy steps complete.
│
◇  Database migrations applied [pre-ship] — app: 0001_init, 0002_add_orders.
│
◇  Uploaded Worker "my-app" (current_version_id: 3f1a9c2e-...).
│
◇  Liveness OK — https://my-app.workers.dev returned 2xx.
│
└  Deployed to cloudflare.

The two commands are the whole lifecycle for a new Cloudflare project: provision runs once to mint the ids the build depends on, and deploy runs every time after — build, secrets, migrations, ship, and a liveness check, in one command.

See also

  • The heximon CLI — the full command surface and the shared flags every command honors.
  • Migrations from the CLI — the generate / migrate / status / push bridge --migrate reuses.
  • Deploy targets — the PlatformHostStrategy each platform package supplies, which this command's target resolution reads.
Copyright © 2026