# Installation

## Prerequisites

| Requirement | Version | Needed for | Notes |
|---|---|---|---|
| Bun | **>= 1.4.4** (`engines.bun`; target 2.0) | everything | the only runtime. No Node, Deno, or workerd support — ever |
| Postgres | 16+ | prod, parity checks | **optional in dev** — `x dev` embeds one in `.x/pg` |
| Docker | any | parity checks, prod images | never required for `x dev`; `x build --target docker` needs it |
| Redis | 7+ | cache tier 3 in prod | dev uses an in-process map behind the same interface |
| NATS | 2.10+ | fanout in prod | dev runs it in-process |
| Native toolchain | — | **nothing** | no native addons in the dependency graph; no `node-gyp`, no `sharp`, no `libvips` |

Bun below the floor fails before anything else runs:

```
X_BUN_VERSION: bun version is below the framework floor
  cause: Bun 1.4.3 is older than the required 1.4.4
  fix:   bun upgrade
```

`x doctor --json` checks the runtime, the embedded Postgres, ports, and the resolved env. Outside an app, `x` is on no `PATH` and `bunx x` fetches an **unrelated** npm package named `x`: spell it `bunx @ultimat3/cli doctor --json` there, and start an app with `bunx create-ultimate myapp`.

| Platform | Status | Proved by |
|---|---|---|
| Linux x64 | supported | every CI job; `scaffold-smoke` gates a fresh scaffold twice per shape |
| macOS | supported | the `macos` CI job: a fresh scaffold's `bun run setup` and its gate on `macos-latest` |
| Windows, native (PowerShell, cmd) | supported | the `windows` CI job: lint, typecheck, a path-heavy unit subset and a scaffold's gate, in pwsh |

| Without Docker | Works |
|---|---|
| `bun run dev`, `bun run setup`, `bun run verify` (the gate) | yes — the database is embedded and no service is dialled ([Bare VM](/bare-vm)) |
| a deploy | takes an image (`x build --target docker` needs Docker, or a host that builds the Dockerfile — [Deploy recipes](/deploy-recipes)), or `x build --target binary` with no container runtime |

## Create an app

```
bunx create-ultimate myapp && cd myapp && bun run setup && bun run dev
```

`create-ultimate` is a thin front for `x new`. Inside an existing workspace, use `x new` directly.

**PowerShell and cmd run the same commands.** `bun run setup` runs the app's `bin/setup.ts` —
TypeScript that Bun executes, no bash and no WSL — and `bun run dev` and `bun run verify` are
`x dev` and `x verify`. Windows PowerShell 5.1 has no `&&`: type the commands one per line there
(PowerShell 7 and cmd take the line as written).

**`bun run setup` is not optional.** `x new` writes files and installs nothing, so `cd myapp && x dev`
stops on `X_BUILD_FAILED` naming `bun install` (measured `As of 2026-08-23`; this page said
otherwise until then). `bun run setup` is six steps, idempotent and safe to re-run:

| # | Step | Why it is in the script |
|---|---|---|
| 1 | `bun install` | `x new` writes files and installs nothing |
| 2 | touch `.env.development.local` | per-box secrets, gitignored, wins over the committed `.env.development` |
| 3 | `x db gen "initial"` | skipped when `packages/db/migrations` already holds a `.sql` — `x db gen` is that directory's only writer |
| 4 | `x db migrate` | applies them, then diffs the live schema against the ledger |
| 5 | `x db seed` | run through the CLI, never `bun run`: the CLI owns the connection, so it reaches the same embedded database the migration just wrote to |
| 6 | `x manifest` | `x.manifest.json` is a projection of the loaded app, so `x new` cannot write it. Nothing else ever ran the command, and `x verify`'s `manifest` step now refuses its absence with `X_MANIFEST_MISSING` |

Every flag, from the registry — `bun run x -- help new`, or `x help new` in an app. There are six,
each with a default, so **`x new` asks nothing**: an agent cannot answer a prompt and is never given
one.

| Flag | Default | Effect |
|---|---|---|
| `--dir <path>` | cwd | parent directory the app is written into |
| `--example` / `--no-example` | `--example` | include the example feature slice; `--no-example` gives the same shape with an empty `app/` |
| `--shape <shape>` | none | start from `api`, `blog` or `dashboard` instead of the dashboard and example slice — what each writes and leaves out: [Shapes](/shapes) |
| `--git` / `--no-git` | `--git` | `git init` and commit the scaffold |
| `--dry-run` | off | print the file list, write nothing |
| `--force` | off | write into a directory that already exists |
| `--json` | off | machine-readable result: the directory, every path written, the git outcome |

There is **no** `--admin`, `--locale`, `--tz`, `--currency`, `--no-install` or `--yes` — this table
listed all six until 2026-08-23 and the parser refuses each with `X_CLI_BAD_FLAG`. `apps/admin/` is
always written; locale, timezone and currency are `app.config.ts` fields you edit after scaffolding
([Configuration](/configuration)).

What `x new` writes, and who owns it afterwards:

| Artifact | Author | Rule |
|---|---|---|
| `.env.development` | generated, **complete and valid**, committed | non-secret defaults; per-box secrets go in `.env.development.local`, which wins. Dev secrets carry a loud `dev-only-` prefix. The scaffold writes no `.env` — Bun still loads `.env`, `.env.<mode>` and `.env.local` when they exist ([`packages/core/src/env-example.ts:31`](https://github.com/developerz-ai/ultimate/blob/main/packages/core/src/env-example.ts)), and `.env` is the one you never commit |
| `.env.example` | generated from `envSchema`, plus the framework's deploy-required secrets | never hand-edited — it is a projection of the declaration, and drift fails `x verify`. Regenerate with `x env example` |
| `app.config.ts` | yours | the one config file; validated at boot ([Configuration](/configuration)) |
| `x.manifest.json` | generated every build | routes, entities, actions, jobs, policies, tags, MCP tools, budgets. Never hand-edited; drift fails `x verify` |
| `openapi.json` | generated | HTTP surface from `action` / `query` declarations |
| `CLAUDE.md`, and `AGENTS.md` a symlink to it | **human-authored stub**, one file | short, terse; a copy only where Windows refuses the link. Ultimate never generates prose docs at runtime |
| `.github/workflows/ci.yml` | generated | `bun run setup` then `bun run verify`, on push and pull request — yours to edit, and the same two commands the app's `README.md` opens with |
| `docker/` | generated | dev compose + per-role prod compose + Dockerfile ([Deployment](/deployment)) |
| `packages/db/migrations/` | **not written at all** | `x db gen` is its only writer, `As of 2026-08`. First commands in a new app: `x db gen "initial"`, then `x db migrate` — `bun run setup` runs both. Until the first one has, `x verify`'s `drift` step is red and names it, for any app declaring an entity; `--no-example` declares none, and zero against zero is agreement |

## Typed env, validated at boot

Declared at module scope in `app.config.ts`, next to `defineConfig`. There is no `env.ts` — `app.config.ts` is the one file the CLI and the runtime both load, so it is the one place the env gate can run before anything binds.

```ts
// app.config.ts
import { defineConfig, defineEnv } from '@ultimat3/core';

export const env = defineEnv({
  DATABASE_URL: { type: 'url' },
  NATS_URL:     { type: 'url', required: false },
  S3_BUCKET:    { type: 'string' },
  VAPID:        { type: 'string', required: false },
  STRIPE_KEY:   { type: 'string', pattern: /^sk_/, secret: true },
});

export const config = defineConfig({ name: 'myapp' /* … */ });
```

Boot parses this before any listener binds. Failure costs ~40ms and exit 1 — not a 3am `undefined` in a payment handler.

```
X_ENV_MISSING: required environment variables are missing or invalid
  cause: STRIPE_KEY missing; NATS_URL is not a URL ("nats:4222")
  fix:   add STRIPE_KEY, NATS_URL to .env (copy .env.example), then run: x env check
```

The `fix:` is per-key and built from your own declarations ([`packages/core/src/env.ts:237`](https://github.com/developerz-ai/ultimate/blob/main/packages/core/src/env.ts)); `defineEnv({ KEY: { fix } })` overrides it for one key.

| Property | Behavior |
|---|---|
| Access | `env.STRIPE_KEY` is typed. A `process.env` read outside the schema is a lint error |
| Per-role | a role requires only the keys it uses — `ROLE=worker` does not fail on a missing `VAPID` |
| Repair | `x env example` regenerates `.env.example` from the schema; copy the keys it names into `.env.development.local` in dev, or into your platform's secret store in production. There is **no** `x env --fix` — `x env` declares `check` and `example` and no flags, so `x env check --fix` dies at the parser with `X_CLI_BAD_FLAG` |
| CI | `x verify`'s `manifest` step holds `.env.example` to the schema's projection (`X_ENV_EXAMPLE_DRIFT`); `x env check` is not a gate step — run it against the deploy's own environment (`ULTIMATE_ENV=production x env check`), where it also reports the framework's deploy-required secrets |
| Provenance | `/_x` → **Env** shows every resolved key and which source it came from |

## What Bun natives replace

Each row is a dependency subtree that never enters the lockfile. Target: **under 40 direct dependencies for the whole framework**.

| Bun primitive | Replaces | Deps killed (approx) |
|---|---|---|
| `Bun.sql` | `pg`, `pg-pool`, `pg-connection-string`, `postgres`, pool wrappers | ~8 |
| `Bun.redis` | `ioredis` / `redis` + command/parser packages | ~5 |
| `Bun.s3` | `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner` + AWS SDK core | ~25 |
| `Bun.serve` WebSockets | `ws`, `socket.io`, `engine.io`, `uWebSockets.js` | ~10 |
| `bun test` | `vitest` / `jest`, `@types/jest`, coverage + mock + snapshot plugins | ~30 |
| `Bun.build` | `esbuild` / `rollup` / `vite` + framework plugin + postcss chain | ~40 |
| `Bun.Transpiler` / macros | `ts-node`, `tsx`, `swc`, babel presets | ~15 |
| `Bun.password` | `bcrypt` / `argon2` native addons | ~4 |
| `Bun.file` / `Bun.write` | `fs-extra`, `graceful-fs`, `globby` | ~6 |
| `bun --hot` | `nodemon`, `concurrently`, HMR middleware | ~5 |

One more subtree dies with no Bun native behind it — a pure-TypeScript framework pipeline, written because native addons are blocked:

| Framework primitive | Replaces | Deps killed (approx) |
|---|---|---|
| `@ultimat3/core` image | `sharp` + libvips + `imagemin` plugins | ~12 |

Stated cost: no native-addon packages, and Bun's long-running-process maturity is less proven than Node's.

## Editor and agent setup

Two MCP servers, and an app ships both. The **dev tools** are `x mcp serve` — `x dev` does not
start them, and nothing listens on 9229 until this command runs. A new app's `.mcp.json` already
names the stdio form, as `bunx @ultimat3/cli mcp serve --transport stdio`, so a client that reads
that file starts it with no setup; [MCP and AI](/mcp-and-ai#client-config) says what to give one that does not.
The **app's own tools** are `POST /mcp` on the app (`apps/web/mcp.ts`), mounted by `x dev` and the
web role, refusing any request without a bearer token. The HTTP form below prints its token:

```
bunx x mcp serve --transport stdio              # a client that spawns the server
bunx x mcp serve --transport http --port 9229   # POST /mcp; the bearer token is printed at boot
```

`stdio` and `http` are the two transports (`x help mcp`) — there is no `ws` transport, and this page
told readers to configure one until 2026-08-23. Register the URL and the printed token with whatever
MCP client you use; a request without the token is answered 401 with no catalog.

The catalog is `bunx x mcp tools --json`: every tool's exact name and description, read off the
server itself rather than counted here. The core of it:

| Tool | Replaces the agent's usual guess |
|---|---|
| `routes.list` | grepping a router directory |
| `schema.describe` | reading migration files in order |
| `policies.list` | "is this endpoint protected?" |
| `actions.describe` | reading `api/` by hand — actions and queries in one call |
| `jobs.inspect` | reading `jobs.ts` for retry policy and step names |
| `queue.depth` | guessing whether the worker is keeping up |
| `manifest.read` | ten separate reads |
| `errors.explain` | a web search for an error string |
| `db.query` | inventing a query and hoping (read-only; 100-row default, 1000-row maximum) |
| `db.migrate` | mutating the dev DB — writes land in a **branch DB only** |
| `tests.run` | parsing terminal output |
| `verify.run` | guessing whether the work is shippable |
| `logs.tail` | scrollback archaeology |

Read tools are unrestricted in dev; write tools are scoped to branch environments. The dev server is never exposed under `ROLE=web`. `db.query` refuses a batch, a write keyword anywhere at statement level (a data-modifying CTE included), a locking clause, `EXPLAIN ANALYZE`, and the banned function families matched by prefix (`pg_read_*`, `pg_ls_*`, `lo_*`, `pg_advisory_*`, `pg_sleep*`, `set_config`, …) — `X_MCP_QUERY_REJECTED`, before the host sees the string. Use `x mcp` for a standalone server (CI, remote agents). Editor config: Biome is the only formatter/linter — one binary, one config, no ESLint or Prettier.

## Upgrading and removal

| Task | Command |
|---|---|
| Framework version bump | `x upgrade --json`, one major per run ([CLI reference](/cli-reference#x-upgrade)). An app below 30.0.0 has no such command yet: `bunx @ultimat3/cli@latest upgrade --json` |
| Check what a bump would change | `x upgrade --dry-run --json`: the pins, the Bun floor edits and the section to read. Writes nothing, runs no gate |
| Remove dev state (embedded PG, storage, caches) | `rm -rf .x` |
| Remove the CLI | it ships with the app; deleting the repo is the uninstall |

Framework ranges: an app declares every `@ultimat3/*` package at `^<version>`, as `x new` writes it, and `bun.lock` pins the exact build. Keep them on one version: the packages move in lockstep, and `x upgrade` moves them together. A third-party wrapper declares them as `peerDependencies` at `^<major>` ([Publish it](/building-your-own-base#publish-it)).

Version pinning: `As of 2026-11`, the Bun floor is the one stated at the top of this page and 2.0 the target; SolidJS 2 is in beta and is pinned exactly, and its upgrade is framework work, not app work. There is no ArkType or Drizzle pin to carry — `@ultimat3/schema` ships its own dependency-free validators and `@ultimat3/entity` its own `postgresDriver()`. Details and codemod inventory in [Upgrading](/upgrading). Boot failures and port conflicts in [Troubleshooting](/troubleshooting).
