Setup
Learn how to go from a fresh clone to a running store with a seeded catalog and a working admin sign-in.
This guide follows the quick start in the repository's README.md: clone, install, fill .env.local, push the schema, seed, run.
The steps are ordered so that a setup failure is distinguishable from a configuration failure. If the schema applies but sign-in does not work, you know the problem is a key, not the install.
Prerequisites
Six things have to exist before the first command. This is the README.md requirements list, with what each one is for.
Node.js 20 or newer. The runtime the production build targets.
Bun. The package manager and script runner. packageManager pins bun@1.2.2, and .github/workflows/verify.yml installs that version.
PostgreSQL 15 or newer. Installed locally, or via the included compose.yml.
Redis / Upstash Redis. REDIS_REST_URL and REDIS_REST_TOKEN are required variables everywhere, and Redis is a required service in production — logins and magic links rate-limit through it. checkRateLimit in lib/rate-limit/presets.ts returns success immediately when isDev, so locally the two values only need to be set, not to point at a live instance.
An S3-compatible bucket. AWS S3, Cloudflare R2, or MinIO locally.
A Resend account. Sign-in is a magic link delivered by email, so without a working key you cannot log in at all — including into the admin.
Stripe, analytics, Shopify, AI providers and newsletter providers are optional. The store browses and carts without any of them.
Step 1: Clone and install
git clone https://github.com/litestore/litestoreClones the repository.
cd litestoreEverything after this runs from the repository root.
bun installInstalls dependencies, and runs two hooks on the way through.
postinstall calls db:generate (prisma generate), which writes the typed client the rest of the app imports.
prepare calls lefthook install, which activates the pre-commit and pre-push gates.
One thing bun install does not do is build the icons. The icon sprite and types/icons.d.ts are generated and gitignored, so run this once after cloning:
bun run iconstypecheck and the sprite contract test both read those files, so a fresh clone fails them until this has run.
Prisma model 'does not exist'?
That error means the generated client is stale, not that the schema is wrong.
Run bun run db:generate and it will resolve.
Step 2: Start the database
If you have Postgres installed locally, create a database named litestore and skip ahead.
Otherwise compose.yml defines Postgres and PgBouncer:
docker compose up -dStarts both services in the background.
docker compose psShows their state, which matters here because the two do not come up together.
Postgres publishes 5432 and is the only service with a healthcheck (pg_isready). PgBouncer publishes 6543 and starts once Postgres reports healthy.
Connect straight to 5432 for local development. PgBouncer exists for production pooling.
Both services read DB_USER, DB_PASSWORD and DB_NAME from your env file, defaulting to postgres / postgres / litestore.
Step 3: Configure environment variables
Copy the example file:
cp .env.example .env.local.env.example ships most keys as empty strings, and env.ts sets emptyStringAsUndefined — so an empty value counts as unset. Copying the file is not the same as filling it in.
This is the minimum that satisfies validation:
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
NEXT_PUBLIC_SITE_EMAIL="hello@example.com"
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/litestore"
BETTER_AUTH_URL="http://localhost:3000"
BETTER_AUTH_SECRET="replace-with-a-long-random-secret"
# Required to be SET even locally — validation fails closed without them.
# The rate limiter never calls Redis in dev, so placeholders are fine here.
REDIS_REST_URL="https://example.upstash.io"
REDIS_REST_TOKEN="..."
RESEND_API_KEY="re_..."
RESEND_SENDER_EMAIL="no-reply@example.com"
S3_REGION="us-east-1"
S3_BUCKET="litestore"
S3_ACCESS_KEY="..."
S3_SECRET_ACCESS_KEY="..."Every route 500s with 'Invalid environment variables'?
A required variable is missing or empty. The validator runs at module load,
so a single absent value fails every request — the two Redis variables and
NEXT_PUBLIC_SITE_EMAIL are the ones most often forgotten. The error output
names the exact keys.
Step 4: Apply the schema
bun run db:pushCreates the tables and applies every committed migration.
README.md names db:push, but in package.json it runs prisma migrate dev — the identical command db:migrate runs. Neither is a schemaless push, and either one writes a migration file.
prisma/migrations/ holds two committed migrations:
20260726143000_baseline_with_db_guarantees
20260730090000_performance_indexesThe baseline carries the schema plus the CHECK constraints and case-insensitive unique indexes.
Step 5: Seed the catalog
bun prisma/seed.tsLoads a working store — products with variants, inventory, media, pages, reviews, feeds and email templates.
README.md names bun run db:seed here, but that script is tsx prisma/seed.ts and tsx is not a dependency in package.json, so it fails unless you have tsx installed globally. The command above runs the same file, and is what the prisma.seed hook uses.
The seed prints its own summary: two rows for each of 41 base models, 20 further products carrying 80+ variants, and 12 transactional email templates.
Orders are not seeded. Create them through checkout or the admin.
Every write is an upsert, so running the seed twice updates rather than duplicates.
Step 6: Start the dev server
bun run devRuns next dev -p 3000 --turbopack.
Open http://localhost:3000 for the storefront and http://localhost:3000/admin for the admin. One process serves both.
Step 7: Sign in and verify
The seed creates an admin account for admin@example.com. Enter that address at /auth/login and follow the emailed link — sign-in is a magic link, so the mail lands wherever your Resend key delivers.
Then open Settings → Developer.
The Self-host readiness panel runs a live SELECT 1 against Postgres and a redis.ping(), each with a 3-second timeout. It also reports whether the site URL is https, and whether Resend, S3, Stripe and an AI provider are configured.
With placeholder Redis values the panel will report Redis down. That is expected locally, and not expected in production.
Troubleshooting
The magic link email never arrives. Check the Resend dashboard for the send. If the send is missing entirely, RESEND_API_KEY or RESEND_SENDER_EMAIL is wrong — the readiness panel will show it.
Typecheck runs out of memory. bun run typecheck asks Node for a 12 GB heap. On smaller machines, run it alone rather than alongside the dev server.
Logins work locally but are blocked once deployed. Env validation only proves a value exists. The rate limiter reads Redis on the auth path, and it is skipped in dev, so a wrong REDIS_REST_URL or REDIS_REST_TOKEN first surfaces in production as a login that never completes.
The readiness panel's redis.ping() is the check that catches it — which is the general shape of this whole setup. A variable being present is not evidence that it is correct; the readiness panel is the only step that actually calls the services.
Next Steps
Configuration
Learn which environment variables env.ts requires, which ones are optional, and what each optional group configures.
Development
Learn how to run Litestore day to day: starting Postgres and the Turbopack dev server, reseeding with db:seed and db:reset, and the checks bun run verify runs before you commit.
Deploying
Learn how to take a configured store to production: bun run db:deploy for migrations, the Next.js build, and the Inngest functions served from /api/inngest.