Plain is headed towards 1.0! Subscribe for development updates →

  1# plain.dev
  2
  3A single command that runs everything you need for local development.
  4
  5![Plain dev command example](https://github.com/dropseed/plain/assets/649496/3643bb64-a99b-4a8e-adab-8c6b81791ea9)
  6
  7The `plain.dev` package can be [installed from PyPI](https://pypi.org/project/plain.dev/), and does _not_ need to be added to `INSTALLED_PACKAGES`.
  8
  9- [`plain dev`](#plain-dev)
 10- [`plain dev services`](#plain-dev-services)
 11- [`plain dev logs`](#plain-dev-logs)
 12- [`plain pre-commit`](#plain-pre-commit)
 13- [`plain contrib`](#plain-contrib)
 14- [VS Code debugging](#vscode-debugging)
 15
 16## `plain dev`
 17
 18The `plain dev` command does several things:
 19
 20- Sets `PLAIN_CSRF_TRUSTED_ORIGINS` to localhost by default
 21- Runs `plain preflight` to check for any issues
 22- Executes any pending model migrations
 23- Starts `gunicorn` with `--reload`
 24- Serves HTTPS on port 8443 by default (uses the next free port if 8443 is taken and no port is specified)
 25- Runs `plain tailwind build --watch`, if `plain.tailwind` is installed
 26- Any custom process defined in `pyproject.toml` at `tool.plain.dev.run`
 27- Necessary services (ex. Postgres) defined in `pyproject.toml` at `tool.plain.dev.services`
 28
 29### Services
 30
 31Use services to define databases or other processes that your app _needs_ to be functional. The services will be started automatically in `plain dev`, but also in `plain pre-commit` (so preflight and tests have a database).
 32
 33Ultimately, how you run your development database is up to you. But a recommended starting point is to use Docker:
 34
 35```toml
 36# pyproject.toml
 37[tool.plain.dev.services]
 38postgres = {cmd = "docker run --name app-postgres --rm -p 54321:5432 -v $(pwd)/.plain/dev/pgdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=postgres postgres:15 postgres"}
 39```
 40
 41### Custom processes
 42
 43Unlike [services](#services), custom processes are _only_ run during `plain dev`. This is a good place to run something like [ngrok](https://ngrok.com/) or a [Plain worker](../../../plain-worker), which you might need to use your local site, but don't need running for executing tests, for example.
 44
 45```toml
 46# pyproject.toml
 47[tool.plain.dev.run]
 48    ngrok = {command = "ngrok http $PORT"}
 49```
 50
 51## `plain dev services`
 52
 53Starts your [services](#services) by themselves.
 54Logs are stored in `.plain/dev/logs/services/`.
 55
 56## `plain dev logs`
 57
 58Show output from recent `plain dev` runs.
 59
 60Logs are stored in `.plain/dev/logs/run/`.
 61
 62```bash
 63plain dev logs        # print last log
 64plain dev logs -f     # follow the latest log
 65plain dev logs --pid 1234
 66plain dev logs --path
 67```
 68
 69## `plain pre-commit`
 70
 71A built-in pre-commit hook that can be installed with `plain pre-commit --install`.
 72
 73Runs:
 74
 75- Custom commands defined in `pyproject.toml` at `tool.plain.pre-commit.run`
 76- `plain code check`, if [`plain.code`](https://plainframework.com/docs/plain-code/plain/code/) is installed
 77- `uv lock --locked`, if using uv
 78- `plain preflight --database default`
 79- `plain migrate --check`
 80- `plain makemigrations --dry-run --check`
 81- `plain build`
 82- `plain test`
 83
 84## VS Code debugging
 85
 86![Debug Plain with VS Code](https://github.com/dropseed/plain-public/assets/649496/250138b6-7702-4ab6-bf38-e0c8e3c56d06)
 87
 88Since `plain dev` runs multiple processes at once, the regular [pdb](https://docs.python.org/3/library/pdb.html) debuggers don't quite work.
 89
 90Instead, we include [microsoft/debugpy](https://github.com/microsoft/debugpy) and an `attach` function to make it even easier to use VS Code's debugger.
 91
 92First, import and run the `debug.attach()` function:
 93
 94```python
 95class HomeView(TemplateView):
 96    template_name = "home.html"
 97
 98    def get_template_context(self):
 99        context = super().get_template_context()
100
101        # Make sure the debugger is attached (will need to be if runserver reloads)
102        from plain.dev import debug; debug.attach()
103
104        # Add a breakpoint (or use the gutter in VS Code to add one)
105        breakpoint()
106
107        return context
108```
109
110When you load the page, you'll see "Waiting for debugger to attach...".
111
112You can then run the VS Code debugger and attach to an existing Python process, at localhost:5678.