Bare VM - developerz-ai/ultimate GitHub Wiki
A fresh Ubuntu box with bun and git runs a scaffolded app's whole gate. No Docker daemon, no
Postgres, no Redis, no NATS, no object store, no .env editing. Bun runs all four commands below;
git is what commits the scaffold, and --no-git is the form that skips it.
bunx create-ultimate demo --no-git
cd demo
bun run setup
bun run check| Command | What it needs from the box |
|---|---|
bunx create-ultimate demo --no-git |
bun, and a network the npm registry answers on. --no-git skips git init, which is the form an automated provisioner uses; without it the scaffold is committed, and that is git's only job here |
cd demo |
โ |
bun run setup |
bun. Six steps: bun install, an .env.development.local touch, x db gen "initial" when packages/db/migrations holds no .sql, x db migrate, x db seed, x manifest
|
bun run check |
bun. x build --target static, then x verify โ the build first, because budgets weighs .x/build-stats.json and the build is that file's only writer |
x new installs nothing, so bun run setup is not optional and cd demo && bun run check stops on
X_BUILD_FAILED naming bun install (Installation).
The scaffold's .env.development ships committed non-secret defaults, and it leaves DATABASE_URL
empty. An empty DATABASE_URL is not a hole to fill โ it is the switch that selects the embedded
database, in resolveServices
(packages/cli/src/runtime-bindings.ts),
which is the one place the three service bindings are decided:
| Binding | Unset env | Resolves to | Set it to |
|---|---|---|---|
| db | DATABASE_URL |
pglite://<app>/.x/pgdata โ Postgres compiled to WASM, running inside this process
|
a postgres: URL |
| events | NATS_URL |
inproc://events โ in-process fanout |
a NATS URL |
| storage | S3_ENDPOINT |
file://<app>/.x/storage |
an S3 endpoint |
The database is
packages/db/src/pglite.ts:
a real Postgres, WASM-compiled, opened on a directory under .x/, so x db migrate and x db seed
write to the same engine the app then reads โ no container to wait for, no port to publish, and a
restart keeps the data. The module is an optional peer resolved at first query, never at import,
so an image that only ever talks to a managed Postgres does not carry the WASM it will never load.
@electric-sql/pglite is a devDependencies entry of the generated app, which is what puts it on
the box during step 1 of bun run setup.
Its absence is a coded refusal, never a silent skip: X_DB_UNAVAILABLE, whose fix: is
bun add @electric-sql/pglite, or set DATABASE_URL to a Postgres server and re-run โ a command the
reader runs, on a box that has just told them what is wrong.
x doctor says it earlier than the first query does. Its external-database probe answers nothing
where DATABASE_URL is unset, and that silence is exactly this box, so it asks the embedded
question too: does the peer resolve from the app root โ a resolve, never an import, since
loading it boots the WASM and takes the single-writer lock the next command needs. Red only where
DATABASE_URL is unset and the peer is unresolvable (CLI reference).
bun run check's live, job and e2e steps need no service of their own in a generated app: the
queue is Postgres (SELECT โฆ FOR UPDATE SKIP LOCKED), the fanout is in-process, and both resolve
through the table above.
No logical replication. PGlite has no walsender, so there is no write-ahead log to decode and no
slot to take. A --live query still works: x dev installs the in-process bridge instead โ the
same row observer the framework's own live tests run on โ and the boot line says which feed you
got, live=in-process under the embedded database and live=replication under a real one
(packages/cli/src/runtime-live-feed.ts). Its honest bound is that a write made by another process
is invisible to it, which holds by construction under x dev, where every role is this one process
(Realtime).
Reach for the dev compose file only when you want parity against a real Postgres, NATS and S3 (Deployment). Nothing on this page uses it.
On a WSL2 developer box โ not a bare VM, and not a CI runner โ on a Bun the CLI's own floor
accepts (x doctor), against real x new scaffolds, first pass, nothing waived and no fix-follow,
warm Bun cache, As of 2026-09-11:
| Measure | Default scaffold | --no-example |
|---|---|---|
| files written | 161 | 133 |
bun run setup |
6,802ms | 5,135ms |
bun run check |
5,389ms | 3,909ms |
the first bun run check's verdict |
green, 20 of 20 steps, budgets included |
green, 20 of 20 steps, budgets included |
The cache is the variable to watch, not the box: a cold first install takes the bun install
inside bun run setup to 12.0s on the same machine.
And on a free ubuntu-latest runner, which is the measurement that speaks for a provisioned
box โ ci.yml's scaffold-smoke job, running these same two scripts on a scaffold written outside
the checkout, As of 2026-09-12:
| Measure | Default scaffold | --no-example |
|---|---|---|
| files written | 161 | 133 |
bun run setup |
6,665ms | 5,994ms |
bun run check |
7,279ms | 4,886ms |
the first bun run check's verdict |
green, 20 of 20 steps pass | green, 20 of 20 steps pass |
budgets is green on that first pass on every one of those runs, because bun run check builds before
it verifies โ nothing is waived to get there, and no printed fix: is followed before the verdict
is taken. The job prints the table itself, one row per command and one per gate step, so the run is
its own measurement rather than a number this page asserts: read it from the job's log when you want
today's.