Start a Sillo application from the official starter (with sillo-start or a plain git clone) and understand what you get and why it is shaped that way.
Creating a Project
Section titled “Creating a Project”Sillo does not generate applications. It publishes one.
sillohq/starter is a working
application (session authentication against a real user model, an admin panel,
migrations, server-rendered pages, a JSON API, a queue) that you copy and make
yours. Its CI boots it and calls every route on every push, on three Python
versions.
That distinction is the whole design, and the rest of this page explains why it is worth caring about.
Install sillo-start
Section titled “Install sillo-start”sillo-start is a separate tool, installed once and used for every project
after that.
uv tool install sillo-startInstalls it as a standalone command on your PATH, in its own isolated environment. Nothing is added to any project.
pip install sillo-startInstall it globally, or into whichever environment you keep your tooling in. It is a small command-line tool with two dependencies, not a runtime dependency of anything you build.
Python 3.11 or newer. Check it is there:
sillo-start --helpCreating one
Section titled “Creating one”sillo-start create-app myappcd myappThen install dependencies and set the database up:
uv syncuv run sillo db:migrateuv run uvicorn app:app --reloadpython -m venv .venvsource .venv/bin/activate # Windows: .venv\Scripts\activatepip install -e ".[dev]"
sillo db:migrateuvicorn app:app --reloadThen open http://localhost:8000.
sillo-start fetches the starter, renames the project to yours, and writes a
.env with freshly generated secrets. It prefers uv when it is installed
and falls back to pip when it is not, so neither path needs anything the
other does.
Everything after that is the sillo command, which the project has as soon as
dependencies are installed. sillo on its own lists what this project can do.
The starter ships a uv.lock, so uv sync reproduces the exact set of
versions its CI tested. Poetry and PDM install it too (the pyproject.toml is
standard PEP 621) but they resolve fresh rather than reading that lock.
Choosing a starter
Section titled “Choosing a starter”create-app takes the repository as an argument. Any public GitHub
repository works, so your own team template is a first-class option.
sillo-start create-app myapp # sillohq/startersillo-start create-app sillohq/starter-inertia myapp # the React onesillo-start create-app sillohq/starter@v1.2 myapp # pinned to a tagsillo-start create-app acme/our-template myapp # your own| Option | |
|---|---|
--ref <branch|tag> | Which revision to take. Defaults to main |
-d, --directory <path> | Where to create it. Defaults to ./<name> |
--install | Install dependencies straight away |
--no-git | Do not initialise a git repository |
--force | Allow a directory that is not empty |
-v, --verbose | Show tracebacks |
Dependencies are not installed by default, so creating a project takes a
second rather than a minute. --install does it during creation if you would
rather not run the install step yourself.
Or clone it
Section titled “Or clone it”The starter is a real repository, so cloning works and gives you the same
application. The one difference is that .env is not written for you:
git clone https://github.com/sillohq/starter.git myappcd myappcp .env.example .env # then set SECRET_KEY to something of your ownThen install and migrate as above.
Prefer this when you want to keep pulling from upstream. Prefer create-app
when you want the project named after itself from the first commit, with no
upstream history to delete.
Requirements
Section titled “Requirements”- Python 3.11 or newer
uv is recommended and is what these guides use,
but nothing here requires it. venv and pip ship with Python and do the same
job.
SQLite needs nothing else. PostgreSQL or MySQL need a running server and one extra driver. See Database & Migrations.
Why a starter repository, not a generator
Section titled “Why a starter repository, not a generator”A generator renders templates. Templates get checked for rendering, which is not the same as working.
A generated project can produce valid Python, import cleanly, render every page, and still fail on its first real request. All of these render perfectly:
- middleware registered in an order that puts authentication outside the session it reads from
- an auth backend reading the
idclaim from a token that carriessub - a
/staticmount that was never added, so every stylesheet 404s in production - a queue that accepts jobs and never runs them
Every one of those was a real bug in this project’s history, and none of them is visible in a rendered template. They surface when something calls the application.
So the starter is an application, and its CI runs it:
- name: Apply migrations run: | cp .env.example .env uv run sillo db:migrate
- name: Test run: uv run pytest -q
- name: Create an administrator env: ADMIN_PASSWORD: Ci-password1! run: uv run sillo user:admin ci@example.com ci
- name: Boot the application run: uv run python scripts/smoke.pyThe last step boots the app and calls every route. What you clone has been run, not merely written.
It has a second consequence worth knowing: a bug in what you get can be fixed without releasing a tool. The starter is a repository; a fix is a commit. Nothing has to be published to PyPI and nothing on your machine has to be upgraded before the next person who clones it gets the fix.
What you get
Section titled “What you get”| Auth | Session-based over JSON, with JWT written and commented out |
| Users | One User model with a manager, password hashing and verify_credentials |
| Admin | Mounted at /admin/, authenticating against that same User |
| Database | Record with SQLite by default, and real migrations |
| Pages | One Jinja template and a stylesheet, with /static served in development |
| API | JSON routes under /api, documented by Atlas at /docs |
| Queue | A worker and scheduler, wired and switched off |
| Console | sillo: migrations, users, worker, scheduler, serve |
| Tooling | ruff, pytest, a smoke check, and CI on three Python versions |
Two things are deliberately not there, and both are covered later:
- No background jobs.
app/jobs/is an empty package. What a job should do is your application’s business, and an example you have to delete is worse than none. See Background Work. - No second user model. The admin signs people in with your
User. See Users & Authentication.
What happens when you create a project
Section titled “What happens when you create a project”Whichever route you take, the same four things happen. Worth knowing because each one is a decision you may want to change.
The files arrive
Section titled “The files arrive”create-app downloads a tarball from
codeload.github.com/<owner>/<repo>/tar.gz/<ref>, rather than cloning.
That needs no git on the machine, brings no history for you to delete
before your first commit, and pins to a tag as easily as to a branch.
GitHub wraps the archive in a single top-level directory named after the repository and commit; that prefix is stripped so the project’s files land at your directory’s root.
An archive member whose path escapes the destination is refused rather than sanitised. That is how a malicious archive overwrites files elsewhere on your machine.
It takes your name
Section titled “It takes your name”Rewriting is targeted, not a blanket find-and-replace, so prose that happens to say “starter” (a README sentence, a comment) is left as written. What changes:
| File | What |
|---|---|
pyproject.toml | name = "myapp" |
app/config.py | app_name, the module docstring, the SQLite path |
.env.example | APP_NAME, the SQLite path, the header comment |
uv.lock | The project’s own name, so uv sync still resolves |
Model files are deliberately excluded. A model’s docstring becomes its
table_description in the database, so rewriting one puts your models out
of step with the committed migration, and the next sillo db:make
writes a spurious second one describing nothing but a changed comment.
It gets its own secrets
Section titled “It gets its own secrets”.env is created from .env.example with a fresh value for
SECRET_KEY, JWT_SECRET and APP_KEY.
A secret committed to a starter is a placeholder by definition. Without
this step every project created from it would sign its sessions with a
key published on GitHub. Cloning directly skips this step, which is why
.env.example has to be copied and edited by hand.
An existing .env is never touched. It may hold real credentials.
It becomes a git repository
Section titled “It becomes a git repository”git init, unless you pass --no-git. No initial commit is made; the
first commit is yours.
The sillo-start tool
Section titled “The sillo-start tool”sillo-start has one command. That is a design decision, not an
omission.
A tool that also manages projects (generating models, editing config, running migrations, supervising processes) has to keep working against every version of every project it ever generated. A tool that only creates them is finished the moment the files land.
So everything a project needs after it exists comes from the framework’s
sillo command, which reads it off the application:
sillo db:migratesillo user:admin ada@example.com adasillo queue:workuvicorn app:app --reloadsillo finds the application and derives those from what it set up. The
operations underneath stay plain functions (sillo.record.commands,
sillo.users.commands, sillo.work.commands) so a project that wants
different names can build its own console against them. See The
Console.
Nothing in a created project depends on sillo-start. You can delete
it the moment your project exists, and a change to it can never break
what it already made.
Using your own starter
Section titled “Using your own starter”Any public GitHub repository works:
sillo-start create-app acme/our-template myappFor a company template (your own base models, your own middleware stack, your own deployment files) fork the starter, change what you want, and point people at yours. The rename rules look for these files, and skip any that are absent:
RENAMES = ( ("pyproject.toml", ('name = "{old}"', 'name = "{new}"')), ("app/config.py", ('"""Typed settings for {Old}."""', '"""Typed settings for {New}."""')), ("app/config.py", ('app_name: str = "{Old}"', 'app_name: str = "{New}"')), ("app/config.py", ("sqlite://storage/{old}.db", "sqlite://storage/{new}.db")), (".env.example", ("# {Old} environment.", "# {New} environment.")), (".env.example", ("APP_NAME={Old}", "APP_NAME={New}")), (".env.example", ("sqlite://storage/{old}.db", "sqlite://storage/{new}.db")),){old}/{new} are the lowercase names, {Old}/{New} the title-cased
ones. Keep those lines recognisable in your fork and renaming keeps
working; change them and the project simply keeps the template’s name,
which you can fix by hand.
Your first five minutes
Section titled “Your first five minutes”sillo db:migrate # create the databasesillo user:admin you@example.com you # an account that can reach /admin/uvicorn app:app --reload # http://localhost:8000sillo on its own lists every command this project has.
Then:
- http://localhost:8000: the welcome page
- http://localhost:8000/docs: the generated OpenAPI documentation
- http://localhost:8000/admin/: the admin panel, note the trailing slash
app/bootstrap.py is the one file worth reading first. Everything the
application is made of is assembled there, in order, with the reasoning
written down beside each step.
Things that will bite you
Section titled “Things that will bite you”Collected from actually running this, not from reading the source.
-
Make sure you are running the project’s own
sillo. A virtual environment activated in a parent directory shadows the project’s, and the sillo it finds there is usually older than the project needs.uv run sillosettles it under uv; undervenv, activate the project’s own.venvfirst. -
Admin routes need the trailing slash.
/admin/login/, not/admin/login. -
Installing dependencies is not the same as creating the database. Installing gets you the
sillocommand;sillo db:migrateis what creates the schema. -
The database file is gitignored, the migrations are not. Commit
database/migrations/; it is the schema’s source of truth.
Related
Section titled “Related”- Project Structure: every directory, and why it exists
- The Console: every command in full
- Database & Migrations: models, migrations, other databases
- Users & Authentication: the one user model
- The Admin Panel: registering models, who may enter
- Background Work: jobs, workers, scheduled tasks
- Testing: the suite, the smoke test, and what each is for
- Deployment: taking it to production