Getting started

The commands create a repository, publish it as an app, and serve a page on a District target. They use production. district login --staging pins staging for every later command, so the rest of the guide needs no environment flag.

Install the CLI

The CLI is the npm package @district-platform/cli on District's internal registry. It requires Node.js 22.15 or later and a District Cloudflare Access account, which District staff have. Sign in to the registry, then install it:

# Sign in to the registry once (opens the browser)
npm login --scope=@district-platform --registry=https://npm.platform.district-staging.net
# or with pnpm
pnpm login --scope=@district-platform --registry=https://npm.platform.district-staging.net

# Install the CLI; the same command upgrades it
npm install -g @district-platform/cli
# or with pnpm
pnpm add -g @district-platform/cli

district --version

npm login or pnpm login opens the browser. The page shows your email and a button. Press the button. The client stores the registry token. The install command also upgrades an installed CLI to the latest published version. The token expires with your staging session, after one month at most. Sign in again and install again.

The CLI asks the registry for the latest version once an hour. Until you upgrade, every command ends with a notice that names the newer version and the install command for your package manager. DISTRICT_NO_UPDATE_CHECK=1 stops the check.

Quick start

Steps 1 and 2 run once per machine: district login stores a session, and district git-credential install lets git authenticate through the CLI. district repo create then creates the repository and clones it.

# 1. Sign in (opens the browser; sessions live in the OS keychain)
district login

# 2. Let git authenticate through the CLI (once per machine)
district git-credential install

# 3. Create a repository and clone it
district repo create "My App" --clone
cd my-app

# 4. Add a simple static app
cat > package.json << 'EOF'
{ "name": "my-app", "private": true }
EOF
cat > wrangler.jsonc << 'EOF'
{
  "name": "my-app",
  "main": "worker.js",
  "compatibility_date": "2026-08-01",
  "assets": { "directory": "./public" }
}
EOF
cat > worker.js << 'EOF'
export default { fetch: () => new Response('Hello from the my-app worker') }
EOF
mkdir public
cat > public/index.html << 'EOF'
<!doctype html>
<title>my-app</title>
<h1>Hello, Sage</h1>
EOF

# 5. Push, publish the app, deploy
git add . && git commit -m "hello district" && git push   # just the normal git CLI
district app create
district deploy

The build requires each file:

  • package.json: every build runs pnpm install, which fails without a manifest. A scripts.build entry runs as the build step; without one, that step is recorded as skipped.
  • wrangler.jsonc: main and compatibility_date are required. The build honors main, compatibility_date, compatibility_flags, assets, the plain-text vars, and the addon keys d1_databases, kv_namespaces, and r2_buckets, which Bindings and environments describes. It ignores keys that name the project or drive your own tooling, such as $schema, name, and observability, and it fails on a key that declares another runtime binding or feature, naming the key. wrangler.toml is refused. Use wrangler.jsonc or wrangler.json at the repository root.
  • worker.js: the worker entry that main names. A request that matches a file in public/ gets the file; the rest reach the worker.
  • The build image provides wrangler and pnpm, so the manifest declares no dependencies.

district app create publishes the repository as an app and prints the app's id and its primary target. district deploy then builds the repository and serves the build on that target:

$ district app create
created my-app (happ_01K4…)
hostname my-app.ondistrict.net
$ district deploy --yes
build hbld_01K4… started (re-attach with: district build attach hbld_01K4…)
✔ checkout  succeeded    3 s
✔ install   succeeded   41 s
✔ build     succeeded   96 s
✔ bundle    succeeded    7 s
✔ export    succeeded    2 s
✔ deployment hdep_01K4… succeeded
https://my-app.ondistrict.net/

The last line is the app's primary target URL. A request for / receives public/index.html. A request that matches another file in public/ receives that file. Every other request reaches the worker. Open the URL to read Hello, Sage.

--yes answers the Deploy? [y/N] question that the command asks first. If a clone fails with an authentication message, run step 2.

Sign in on a second machine or in CI

A session belongs to one machine and one environment. district login stores it in the OS keychain of the machine it runs on, so each machine signs in again, and each environment in use signs in separately.

# On the second machine, sign in for each environment you use
district login
district login --staging

# Let git authenticate through the CLI
district git-credential install

# An account in several organizations names the one to act in
district org select org_01K4…

district git-credential install runs once per machine, after the first login. Git then authenticates through the CLI, and no long-lived git token is stored.

An account that belongs to several organizations selects one. district org select <id> changes the organization the session acts in, and it needs no second browser confirmation.

--no-browser prints the URL and opens nothing. Use it on a machine that can open no browser, then confirm the code from another device.

A job with no terminal cannot answer questions. Every command that asks one needs --yes. The job needs a session that a person stored on that machine first. An account in several organizations also runs district org select <id> once, because the job cannot answer the selection prompt.