Getting started

From clone to a running local app you are an admin of, in the order that works — install, secrets, database, dev server, first sign-in.

Prerequisites

  • Bun, the only package manager the repo uses. The version it expects is pinned in package.json under packageManager.
  • Node.js 22 or newer. Wrangler and Vite run on Node even when you start them through bun run.
  • openssl, or any other way to generate a random secret.
  • A Google Cloud project for OAuth credentials, when you want the Google button to work. You can start without one: see step 2.
  • A Cloudflare account, only when you deploy. Local development needs no account.

Everything runs locally: the dev server executes your code in workerd, the same runtime Cloudflare uses in production, with a local D1 database and a local R2 bucket on disk under .wrangler/state.

1. Clone and install

Clone the ShipKit repository your GitHub account was given access to, then install dependencies:

git clone <repository-url> my-app
cd my-app
bun install
bun run cf-typegen     # generates worker-configuration.d.ts, the Env types

worker-configuration.d.ts is gitignored, so every fresh clone needs cf-typegen once for the editor and bun run typecheck to see the Worker's types. If you plan to pull future ShipKit releases into your project, read Upgrading before you rewrite history or rename the remote.

2. Create .dev.vars

Local secrets live in .dev.vars, which is gitignored. .dev.vars.example is the full key list with a comment on each one; copy it:

cp .dev.vars.example .dev.vars
openssl rand -base64 32    # paste the output as BETTER_AUTH_SECRET

Four values are required before the app will serve a request that touches auth. The environment is validated on first use, and a missing or malformed key stops the request with an error naming it.

KeyLocal value
APP_URLhttp://localhost:3000 (already set)
BETTER_AUTH_SECRETThe random string you generated; at least 16 characters
GOOGLE_CLIENT_IDYour Google OAuth client id
GOOGLE_CLIENT_SECRETIts secret

If you do not have Google credentials yet, put any non-empty placeholder in both Google keys. Validation passes, every page renders, and you can sign in with an emailed code; only the Google button fails until real credentials are in place. The redirect URI to register with Google is http://localhost:3000/api/auth/callback/google. For the One Tap prompt, the same client id also goes into googleClientId in src/config/app-config.ts. Authentication covers the Google setup in full.

Everything else in the file is optional and can stay empty for now: GitHub sign-in, the payment provider keys, RESEND_API_KEY. The app runs without them; Configuration lists what each one switches on.

Environment is read when the dev server boots. Restart it after editing .dev.vars.

3. Create the local database

bun run db:migrate:local

This applies every migration in drizzle/ to the local D1 database. There is nothing to provision: the database is a file under .wrangler/state, created on first use.

Optionally, fill it with demo data so the admin console and its charts are not empty:

bun run db:seed            # 90 days of users, subscriptions, orders, credits
bun run db:seed --reset    # removes the seed rows again, and nothing else

Seed rows all have ids starting with seed- and use fake email domains, so they never mix with accounts you create yourself. The data is deterministic, and dates are relative to the day you run it.

4. Start the dev server

bun run dev                # http://localhost:3000

Open http://localhost:3000. The landing page, /pricing, /docs, /blog and their Chinese versions under /zh should all render. /dashboard redirects to /login until you sign in.

If port 3000 is taken, an old dev server is usually still running; stop it before starting a new one.

5. Sign in

Go to /login. There are up to three ways in, and all of them create the account on first use; there is no separate sign-up.

  • Emailed code is always available. Without RESEND_API_KEY nothing is sent; the code is printed to the dev server's terminal instead:

    [auth] no mail provider — code for you@example.com: 123456
    

    Enter it on the login page. Codes expire after ten minutes.

  • Google works once real credentials are in .dev.vars.

  • GitHub appears only when both GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET are set.

After signing in you land on /dashboard.

6. Make yourself an admin

A new account has no role. Promote yours in the local database, using the address you signed in with:

bunx wrangler d1 execute DB --local \
  --command "UPDATE user SET role='admin' WHERE email='you@example.com';"

Reload the page and an Admin entry appears in the sidebar, leading to /admin. The built-in admin role holds every permission; other roles can be created in the console under Settings → Roles, as described in Permissions.

With Claude Code

If you use Claude Code, the /setup skill runs these steps for you: it installs, creates .dev.vars with a generated secret, migrates, starts the dev server, checks that pages respond and promotes your account once you have signed in. The steps that need a browser have their own skills: /google-oauth for the Google credentials, /stripe or /creem for payments, /deploy for Cloudflare. Working with AI agents describes all of them.

Next steps

  • Go through Configuration before anything else. The app name, canonical URL, support address and legal entity in src/config/app-config.ts are placeholders, and they reach the marketing pages, every transactional email and the Terms and Privacy pages.
  • Remove the modules you do not need with Deleting features, while the codebase is still untouched.
  • Wire payments with Payments. Until a provider key is set, purchase buttons say "coming soon" and nothing can be charged.
  • When you are ready to go live, follow Deploy.