parbaked
by saml7n
README.md
# parbaked — agent-native FastAPI scaffold for invite-only Python apps
**A FastAPI starter template with auth, admin dashboard, signup approval workflow, and one-command fly.io deploy — built so an AI coding agent can scaffold and ship the whole app via MCP tool calls.**
[](https://pypi.org/project/parbaked/) [](https://github.com/saml7n/parbaked/blob/main/LICENSE) [](https://www.python.org/downloads/) [](https://glama.ai/mcp/servers/saml7n/parbaked)
Signup with admin approval, JWT sessions, rate limiting, magic-link admin login, SQLite-on-fly-volume persistence, and a [published off-boarding contract](https://github.com/saml7n/parbaked/blob/main/docs/data-format-guarantees.md) — set up in two commands. Ships as both a Python package (`pip install parbaked`) and an MCP server (`claude mcp add parbaked -- uvx parbaked mcp`) so the same kernel works whether you're writing code by hand or having an agent build it.
> Why exists: if you ship PoCs on fly.io, two things bite you — anyone on the internet can spin up accounts in a loop (and run up your bill), and you write the same auth + admin boilerplate every time. parbaked is the slice between "I have an idea" and "this is safe to put online."
**Searchable terms:** FastAPI scaffold, FastAPI starter template, FastAPI boilerplate, Python admin dashboard, FastAPI invite-only auth, FastAPI fly.io deploy, MCP server FastAPI, Python web app scaffold, FastAPI authentication template, Python SaaS starter, FastAPI with admin panel, FastAPI signup admin approval.
## 30-second quickstart
```bash
uv tool install parbaked
parbaked new myapp
cd myapp
parbaked dev
```
## What you get
- Signup + login + JWT sessions
- Admin-approval gate (nobody gets in until you click approve)
- Built-in admin dashboard with pending/active/rejected queues
- Per-IP rate limiting (5/min signup, 10/min login by default)
- SQLite DB on a fly volume (persists across machine restarts)
- Secrets auto-generated on first run, never committed
`parbaked new` scaffolds the project and runs `uv sync` so the first `parbaked dev` is instant. Open `http://localhost:8000/auth/admin` and paste the password from the terminal banner. The admin dashboard, signup/login REST API, and rate-limit middleware are already wired. Drop more `.py` files in `routes/` and they auto-mount.
Once you set `PARBAKED_ADMIN_EMAIL`, the admin login form switches to a one-time magic-link flow that mails sign-in links to that inbox — no shared password to remember. See [Admin auth](#admin-auth).
## What `parbaked new` scaffolds
```
myapp/
├── parbaked.toml
├── routes/
│ └── __init__.py # add .py files here, they auto-mount
├── models.py
├── web/
│ └── index.html # vanilla-JS placeholder — replace freely
├── pyproject.toml
├── .env.example
├── .gitignore
└── README.md
```
No `main.py`. No `Dockerfile`. No `fly.toml`. The kernel runtime owns the entrypoint; `parbaked deploy` generates the deploy targets from `parbaked.toml`. If you ever want to take ownership, `parbaked eject` hands you everything (see below).
## Adding routes
Drop a `.py` file in `routes/` with a module-level `router`:
```python
# routes/notes.py
from fastapi import APIRouter, Depends
from parbaked import current_user
router = APIRouter()
@router.get("/")
def list_notes(user = Depends(current_user)):
return {"user": user.email, "notes": []}
```
Restart the dev server (or wait for hot reload) and `GET /notes/` returns your handler's response. The auto-discovery rule: file path becomes URL prefix. `routes/api/users.py` mounts at `/api/users`. `routes/index.py` mounts at `/`.
## Shipping to fly.io
First-time setup — one command walks you through installing flyctl (if needed), signing in, claiming the app, pushing secrets, and deploying:
```bash
parbaked init
```
`parbaked init` generates a globally-unique fly app name from your `parbaked.toml` (`<app-name>-<5-char-suffix>` — bypasses fly's "name taken" wall on common project names), claims it under your personal org, pushes `.env` / auto-generated secrets via `fly secrets set`, then runs the first deploy. The chosen fly app name + first-deploy timestamp are cached to `.parbaked/deploy.json`, so subsequent redeploys are zero-prompt:
```bash
parbaked deploy
```
That regenerates `Dockerfile` + `fly.toml` into `.parbaked/build/` from your `parbaked.toml`, creates the SQLite volume if it doesn't exist, and runs `fly deploy` against the cached app. The generated `fly.toml` ships with cost-protection defaults — `auto_stop_machines = "stop"`, `min_machines_running = 0`, `max_machines_running = 2` — so an idle deploy costs nothing and a traffic spike can't autoscale you into a bill.
Other helpers:
```bash
parbaked logs # tail fly logs for the cached app
parbaked secrets # push .env entries to fly secrets
parbaked tunnel # cloudflared quick tunnel for sharing localhost
parbaked destroy # permanently destroy the fly app (asks for confirmation)
```
`parbaked init` is for the first deploy of a given project. After that, `parbaked deploy` is the only one-shot you need; the rest are situational. You shouldn't need to run raw `fly` commands directly — if you do, file an issue.
## Off-boarding contract
Every byte parbaked owns is in a public, importable format:
- **Password hashes:** bcrypt 2b, cost 12 — verifiable with any bcrypt-compliant library.
- **Session tokens:** RFC 7519 JWT, HS256 — decodable on jwt.io or any stdlib.
- **Users table:** documented column-by-column in [`docs/data-format-guarantees.md`](https://github.com/saml7n/parbaked/blob/main/docs/data-format-guarantees.md). Additive changes only between major versions.
- **Audit events:** one JSON object per stdout line — ships to any log aggregator.
When you outgrow parbaked, run `parbaked eject`. You get a `parbaked-export/` directory: PostgreSQL-compatible `schema.sql`, CSV data dumps, the generated `Dockerfile` + `fly.toml`, an `.env.example` listing the env-var contract, and a `MANIFEST.md` pointing back at the format docs. No vendor-specific decoders. No lobster trap.
Every release adds a section to [`CHANGELOG.md`](https://github.com/saml7n/parbaked/blob/main/CHANGELOG.md), and breaking changes ship a migration recipe there.
## Configuration
Everything has sensible defaults. Set in `parbaked.toml` (non-secret) or via `PARBAKED_*` env vars (secrets):
| Env var | Default | What it does |
|---|---|---|
| `PARBAKED_JWT_SECRET` | *(auto-generated)* | Session-token signing key. Required in prod. |
| `PARBAKED_APPROVAL_TOKEN_SECRET` | *(auto-generated)* | Magic-link signing key. Required in prod. |
| `PARBAKED_ADMIN_PASSWORD` | *(auto-generated)* | Dashboard login password (no-email mode only — see [Admin auth](#admin-auth)). Ignored when `PARBAKED_ADMIN_EMAIL` is set. |
| `PARBAKED_ADMIN_EMAIL` | unset | The admin's email address. When set, the admin login form switches to a one-time magic-link flow AND signup-approval emails go to this inbox. |
| `PARBAKED_APP_NAME` | `"My App"` | Used in email subjects. |
| `PARBAKED_APP_URL` | `http://localhost:8000` | Public URL for magic links. |
| `PARBAKED_RESEND_KEY` | — | Set this to send real email via Resend. Unset → emails print to stdout. Get a free key (no card) at <https://resend.com/api-keys> or run `parbaked email setup`. |
| `PARBAKED_MAIL_FROM` | `onboarding@resend.dev` | From address. Default is Resend's sandbox. For real users, verify your domain at resend.com/domains. |
| `PARBAKED_RATELIMIT_SIGNUP` | `5/minute` | Per-IP signup limit. |
| `PARBAKED_RATELIMIT_LOGIN` | `10/minute` | Per-IP login limit. |
| `PARBAKED_DATABASE_URL` | `sqlite:///./parbaked.db` | SQLite-only. Standard SQLAlchemy URL. |
Auto-generated secrets get persisted to `.parbaked.json` (chmod 600). In production, set them as env vars instead.
## Admin auth
parbaked has one admin. There are two ways to sign them in, picked per-mode (no hybrids):
- **Email mode** (`PARBAKED_ADMIN_EMAIL` is set) — the admin login form asks for the admin email. Submitting the matching address mails a 15-minute magic link to that inbox. Click → admin session cookie set → dashboard. The admin password is unused. Approve / reject buttons in the approval emails still work as before.
- **No-email mode** (`PARBAKED_ADMIN_EMAIL` is unset) — the form asks for the shared `PARBAKED_ADMIN_PASSWORD` (printed in the boot banner in dev, set as a fly secret in prod). No magic links because there's no transport.
Flipping modes is a single env var: add `PARBAKED_ADMIN_EMAIL` and restart — the login form changes on next boot. No DB migration.
When the admin inbox is unreachable (DNS broke, Resend bouncing, you lost access), shell into the deployment and run `parbaked admin signin`. It prints a one-time signed URL using the local JWT secret:
```bash
# locally
parbaked admin signin
# in prod
fly ssh console -C "parbaked admin signin"
```
Shell access IS the recovery path; there is no shadow password.
## Security posture
What protects you from a bill:
- **Per-IP rate limits** on signup, login, and password reset (slowapi).
- **No email enumeration** — signup with an existing email returns the same 201 + "check your inbox" response as a fresh signup; if the existing account is still pending verification, parbaked re-sends the verify link so a "forgot I signed up" user can actually log in (#314) without the response shape leaking which addresses are registered. Login with a wrong password returns the same generic 401 as an unknown email.
- **Approval gate** — even if someone gets past the rate limit, they can't do anything until you click approve.
- **bcrypt** for passwords, **HS256 JWT** for sessions, **audience-scoped JWT** for magic links so a session token can never be replayed as approval (and vice versa).
- **CSRF double-submit cookies** on every admin POST.
- **Production-mode tripwires** — `PARBAKED_ENV=production` refuses to boot if any required secret was auto-generated, warns on multi-instance setups, suppresses the admin password in the banner.
parbaked assumes **one process, one machine** — see the [single-instance contract in AGENTS.md](https://github.com/saml7n/parbaked/blob/main/AGENTS.md#single-instance-contract) for what breaks when you scale horizontally.
## Running under a different ASGI server
`parbaked dev` runs `uv run uvicorn parbaked.runtime:create_app --factory` (pinning uvicorn + parbaked to the project's pyproject.toml / .venv rather than whatever `uvicorn` happens to be first on $PATH). For Gunicorn / Hypercorn / Granian, write a 2-line `wsgi.py`:
```python
from parbaked.runtime import create_app
app = create_app()
```
Then `gunicorn -k uvicorn.workers.UvicornWorker wsgi:app --workers 4`. See [AGENTS.md](https://github.com/saml7n/parbaked/blob/main/AGENTS.md) for the full advanced section.
## Install
The CLI is a uv tool (one-shot scaffold/dev/deploy):
```bash
uv tool install parbaked
```
If you'd rather embed parbaked as a library dependency in an existing project:
```bash
uv add parbaked
# or
pip install parbaked
```
Requires Python 3.11+.
### Tracking pre-releases
If you want to pull an in-flight alpha or release-candidate (e.g. to test an upcoming `1.4.0a*` before it ships stable), opt in with `--prerelease=allow` and bypass uv's index cache with `--refresh`:
```bash
uv tool install parbaked --prerelease=allow --refresh
# or
uv add parbaked --prerelease=allow
# or
pip install --pre parbaked
```
These flags are not needed on the stable channel — vanilla `uv tool install parbaked` is the recommended path.
## Develop / contribute
```bash
git clone https://github.com/saml7n/parbaked
cd parbaked
uv sync --extra dev
uv run pytest
```
Issues and PRs welcome.
## How does this compare to other FastAPI scaffolds?
The FastAPI scaffolding/template space has a few well-known options. Quick honest comparison:
**vs. [`fastapi/full-stack-fastapi-template`](https://github.com/fastapi/full-stack-fastapi-template)** (the official template) — full-stack-fastapi-template ships React + Postgres + Docker Compose + an SQLAdmin panel, designed for teams building a customer-facing SaaS. It's the right pick if you want a production-grade React+API monorepo from day one. parbaked is the right pick if you want to **ship a single-instance Python web app to fly.io today with invite-only auth as the headline feature** — no React build pipeline, no Postgres bootstrap, no DIY approval workflow. Two commands from zero to deployed.
**vs. [`fastapi-users`](https://github.com/fastapi-users/fastapi-users)** + a separate admin panel — fastapi-users is a solid auth-primitives library but is now in maintenance mode (no new features), and you'd still build the admin UI, the approval queue, the rate limiter, the magic-link flow, the deploy story, and the Dockerfile yourself. parbaked bundles all of that, plus an MCP server so an agent can extend it via tool calls.
**vs. [`sqladmin`](https://github.com/aminalaee/sqladmin)** + DIY auth — sqladmin gives you a Django-style `/admin` for any SQLAlchemy model in ~10 lines. Excellent for adding an admin to an existing app, but you're still building the entire auth + approval + deploy pipeline. parbaked includes sqladmin-grade admin out of the box and adds the auth/deploy/MCP-server pieces.
**vs. [Pocketbase](https://pocketbase.io)** — Pocketbase is a single Go binary with its own auth, admin, realtime, and file storage — drop-and-run. parbaked is a Python scaffolding layer over FastAPI — same single-instance philosophy and admin-dashboard goal, but FastAPI-native (drop in any FastAPI route), no realtime / no file storage primitives yet, and the data layer is plain SQLite + SQLModel so you can `parbaked eject` to real Postgres without parbaked-specific decoders.
**vs. [FastAPI-Amis-Admin](https://github.com/amisadmin/fastapi-amis-admin)** — FastAPI-Amis-Admin is the closest "Django admin for FastAPI" with RBAC built in. Last release was March 2025, docs are heavily Chinese-first. parbaked is English-first, actively maintained (cut today), and has a tighter MCP/agent surface.
**vs. agent-built-from-scratch** — if you let Claude Code or Cursor write a FastAPI app from scratch they'll typically generate ~500 lines covering signup + JWT + an admin route + a Dockerfile + fly.toml + a half-broken approval workflow. parbaked is the same shape, but 32 alphas + a full pen-test suite ahead on security, deploy reliability, and edge cases like password redaction in 422 errors. Cost: you accept parbaked's conventions (flat module layout, magic-link admin login, opinionated rate-limit defaults).
## License
MIT — see [LICENSE](https://github.com/saml7n/parbaked/blob/main/LICENSE).
<!-- mcp-name: io.github.saml7n/parbaked -->
This server cannot be deployed
Maintenance
ActivityNo data
ResponsivenessSyncing