Self-hosting
Run Krafa on your own infrastructure. Services, environment variables, and what degrades gracefully when a credential is missing.
Krafa is a pnpm workspace with two applications and three backing services. Everything except the database and object storage is optional, and the app degrades feature by feature rather than failing to start.
What runs
| Piece | Stack | Dev port |
|---|---|---|
frontend/ | Vite, React 19, Tailwind v4, TanStack Router | 5173 (5174 fallback) |
backend/ | Hono on @hono/node-server, pg | 3000 |
| PostgreSQL | Docker | 5433 |
| MinIO | Docker, S3-compatible | 9002 / 9003 |
The non-standard ports are deliberate, so a Krafa checkout doesn't collide with whatever else you have running.
Running it
Configure the backend
cp backend/.env.example backend/.envAt minimum, set DATABASE_URL, BETTER_AUTH_SECRET, S3_ACCESS_KEY, and S3_SECRET_KEY. See the table below.
Install and run
pnpm install
pnpm devRuns the frontend and backend together. Schema migrations are idempotent and apply on backend startup.
Environment variables
Set these in backend/.env. They're validated at startup with t3-env, so a missing required variable fails immediately with a readable message rather than at first use. Empty strings count as unset.
Required
| Variable | Purpose |
|---|---|
DATABASE_URL | PostgreSQL connection string |
BETTER_AUTH_SECRET | Signs sessions and encrypts stored OAuth tokens. Minimum 32 characters |
S3_ACCESS_KEY | Object storage credentials |
S3_SECRET_KEY | Object storage credentials |
Core
| Variable | Default | Purpose |
|---|---|---|
PORT | 3000 | Backend HTTP port |
BETTER_AUTH_URL | http://localhost:3000 | Auth base URL |
FRONTEND_URL | http://localhost:5174 | Frontend origin, used for CORS and redirects |
S3_ENDPOINT | http://localhost:9002 | S3-compatible endpoint |
S3_BUCKET | krafa-media | Upload bucket |
Taste extraction
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY | Powers extraction. Without it, extraction silently no-ops |
This is the one that turns Krafa from a paste canvas into a taste engine. Without it the canvas works perfectly, boards save, imports run, and the taste panel stays empty forever, with no error to tell you why.
Authentication
| Variable | Purpose |
|---|---|
RESEND_API_KEY | Sends magic links and invites. Without it, links are logged to the backend console |
MAGIC_LINK_FROM | Sender address. Defaults to Krafa <onboarding@resend.dev> |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | GitHub sign-in |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | Google sign-in |
DEMO_EMAIL | Enables a shared demo account at /api/demo-login. Leave unset in production |
OAuth callbacks are {BETTER_AUTH_URL}/api/auth/callback/github and .../google.
Console-logged magic links are the fastest path to a working local instance, since you can sign in with no email provider at all. Just don't ship that way.
Imports
| Variable | Purpose |
|---|---|
X_CLIENT_ID / X_CLIENT_SECRET | X bookmark import. The secret is optional for public clients |
PINTEREST_CLIENT_ID / PINTEREST_CLIENT_SECRET | Pinterest OAuth |
Unset credentials produce a not configured state in the import sheet rather than a broken flow. Bookmark access additionally needs an X API app on a tier that grants it.
Serving MCP
The MCP server is part of the backend, so it's live at {your-origin}/api/mcp with no extra process. Board tokens minted on your instance authenticate against your instance only.
Without Stripe credentials the instance runs unmetered: every account is treated as exempt, so generations are recorded for the usage panel but never counted against an allowance or charged. Locking your own users out of a feature nobody can pay for would be the wrong default.
Behind a reverse proxy, make sure it doesn't buffer responses or strip the Authorization header, since Streamable HTTP needs both to pass through cleanly. Point BETTER_AUTH_URL and FRONTEND_URL at your public origins so OAuth callbacks and invite links resolve correctly.
What lives where
| Data | Storage |
|---|---|
| Boards and canvas nodes | PostgreSQL, boards.nodes as JSONB |
| Taste memories | PostgreSQL, memories |
| MCP tokens | PostgreSQL, boards.mcp_token |
| Uploaded media | S3-compatible object storage, referenced from files |
| Import OAuth tokens | PostgreSQL, encrypted with BETTER_AUTH_SECRET |
Auth tables are managed by the Better Auth CLI; the app's own schema applies automatically on startup.
The only data that leaves your infrastructure is what extraction sends to Anthropic: the reference being analyzed and the board's existing memories. Leave ANTHROPIC_API_KEY unset and nothing leaves at all.