Skip to content

Deploy Your Site

A scaffolded project runs locally against a placeholder database and a localhost passkey origin. Deploying means replacing both with real values and building the site for wherever it runs next. What that looks like depends on the runtime: pnpm create plumix-app scaffolds Node by default, which builds to a directory and a command you run on any host that runs Node, while --runtime cloudflare scaffolds a Worker, which plumix deploy hands to wrangler deploy.

On the Node runtime, plumix build writes dist/client and dist/server/worker.js, and deploying is running that file with node wherever you host it — a VM, a container, anywhere Node runs. There is no separate deploy command, because there is no platform API to call: you own the process.

On the Cloudflare runtime, plumix deploy hands off to wrangler deploy, so what ships is whatever wrangler.jsonc describes plus the bundle plumix build produced.

Either way, three things in a scaffolded project point at localhost and need real values before the site is live: the passkey origin, the database, and any secret you have been reading out of .env or .dev.vars. The sections below cover each runtime’s version of that swap.

This is the path from a scaffolded site (pnpm create plumix-app, the default runtime) to a running process. Node.js covers the runtime in full; this is the shortened version.

  1. Point the passkey at the deployed host. In plumix.config.ts, set rpId to the host you are deploying to and origin to its https:// address (or http:// only if you genuinely serve plaintext, which a passkey ceremony run over the open internet should not).

    import { auth } from "plumix";
    export const recipeAuth = auth({
    passkey: {
    rpName: "Recipes",
    rpId: "recipes.example",
    origin: "https://recipes.example",
    },
    });
  2. Set every secret and environment variable the config resolves. There is no .dev.vars upload step: process.env on the host is the whole story, so set RESEND_API_KEY, GITHUB_CLIENT_SECRET and the rest through whatever your host uses — a .env file the process manager loads, its own secrets UI, or the shell that starts the process. PORT and HOST control what the server listens on, defaulting to 3000 and 0.0.0.0.

  3. Generate and apply the migrations.

    Terminal window
    pnpm exec plumix migrate generate
    pnpm exec plumix migrate apply

    generate writes the resolved schema and emits SQL into drizzle/; apply runs the pending files against the SQLite file nodeSqlite names. Run it again after adding a plugin that carries its own tables. Point it at the production database path, not the one you have been developing against.

  4. Build.

    Terminal window
    pnpm build

    plumix build writes dist/client for the browser and dist/server/worker.js to run. Everything the server needs is inlined into that one file, except native packages, which it imports at runtime.

  5. Run it.

    Terminal window
    PORT=3000 node dist/server/worker.js

    Run this under whatever keeps a process alive on your host — systemd, a container orchestrator, pm2. A SIGTERM stops the process cleanly: it stops accepting new requests and gives in-flight work — requests, a scheduled run, deferred work such as telemetry delivery — one ten-second budget. If everything finishes it exits 0; anything the budget cuts makes it exit 1.

  6. Claim the admin account. Open https://recipes.example/_plumix/admin and create your passkey before anyone else does. The database is empty, so the first registration wins the admin account and every later one is refused.

Node ships no kv, and uploads and the image cache live on the one machine the process runs on. Node.js covers what that means and when plumix/storage/s3 or plumix/db/libsql are worth swapping in instead of the disk-backed defaults. Public pages render on every request until you put a CDN in front of the process and declare the cdn slot, which is the same declaration a Worker carries — CDN Caching is that path end to end.

plumix doctor prints what the CLI resolved from your config, which is the runtime name, the database kind, the plugins with their versions, and a schema tables count. That count sums the tables the plugins declare and nothing else, so a project with no schema-carrying plugin reports 0 even though core’s entries, terms, users, sessions and credentials are all in the generated schema. Run it before a deploy after adding a plugin, to confirm its tables arrived. This works the same way on both runtimes.

Deployment is the section this page compresses, and it covers what each step here glosses over. Node.js and Cloudflare Workers cover each runtime’s slots one at a time. Bindings and Environment covers how a Cloudflare binding in wrangler.jsonc reaches a config slot. Secrets covers .dev.vars, EnvInput and production secrets in full. Configuration is the reference for the slots you edited above. Passkeys covers the relying-party id, allowed origins and adding a second device.

Read Deployment next for the parts this page names but does not explain, particularly Bindings and Environment and Secrets. If the site is live but empty, Content Modelling is where the model gets built, and Plugins covers installing Blog or Pages if you want their entry types rather than your own.