1# plain.dev
2
3**A single command that runs everything you need for local development.**
4
5
6
7- [Overview](#overview)
8- [Commands](#commands)
9 - [`plain dev`](#plain-dev)
10 - [Services](#services)
11 - [Custom processes](#custom-processes)
12 - [`plain dev services`](#plain-dev-services)
13 - [`plain dev logs`](#plain-dev-logs)
14 - [`plain pre-commit`](#plain-pre-commit)
15- [VS Code debugging](#vs-code-debugging)
16- [Installation](#installation)
17
18## Overview
19
20The `plain.dev` package provides development tools for Plain applications. The main command, `plain dev`, starts everything you need for local development with a single command:
21
22```bash
23plain dev
24```
25
26This will:
27
28- Run preflight checks
29- Execute pending migrations
30- Start your development server with auto-reload
31- Build and watch CSS with Tailwind (if installed)
32- Start required services (like databases)
33- Run any custom processes you've defined
34
35## Commands
36
37### `plain dev`
38
39The `plain dev` command does several things:
40
41- Sets `PLAIN_CSRF_TRUSTED_ORIGINS` to localhost by default
42- Runs `plain preflight` to check for any issues
43- Executes any pending model migrations
44- Starts `gunicorn` with `--reload`
45- Serves HTTPS on port 8443 by default (uses the next free port if 8443 is taken and no port is specified)
46- Runs `plain tailwind build --watch`, if `plain.tailwind` is installed
47- Any custom process defined in `pyproject.toml` at `tool.plain.dev.run`
48- Necessary services (ex. Postgres) defined in `pyproject.toml` at `tool.plain.dev.services`
49
50#### Services
51
52Use 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).
53
54Ultimately, how you run your development database is up to you. But a recommended starting point is to use Docker:
55
56```toml
57# pyproject.toml
58[tool.plain.dev.services]
59postgres = {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"}
60```
61
62#### Custom processes
63
64Unlike [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 job worker](../../../plain-jobs), which you might need to use your local site, but don't need running for executing tests, for example.
65
66```toml
67# pyproject.toml
68[tool.plain.dev.run]
69 ngrok = {command = "ngrok http $PORT"}
70```
71
72### `plain dev services`
73
74Starts your [services](#services) by themselves.
75Logs are stored in `.plain/dev/logs/services/`.
76
77### `plain dev logs`
78
79Show output from recent `plain dev` runs.
80
81Logs are stored in `.plain/dev/logs/run/`.
82
83```bash
84plain dev logs # print last log
85plain dev logs -f # follow the latest log
86plain dev logs --pid 1234
87plain dev logs --path
88```
89
90### `plain pre-commit`
91
92A built-in pre-commit hook that can be installed with `plain pre-commit --install`.
93
94Runs:
95
96- Custom commands defined in `pyproject.toml` at `tool.plain.pre-commit.run`
97- `plain code check`, if [`plain.code`](https://plainframework.com/docs/plain-code/plain/code/) is installed
98- `uv lock --locked`, if using uv
99- `plain preflight`
100- `plain migrate --check`
101- `plain makemigrations --dry-run --check`
102- `plain build`
103- `plain test`
104
105## VS Code debugging
106
107
108
109Since `plain dev` runs multiple processes at once, the regular [pdb](https://docs.python.org/3/library/pdb.html) debuggers don't quite work.
110
111Instead, we include [microsoft/debugpy](https://github.com/microsoft/debugpy) and provide debugging utilities to make it easier to use VS Code's debugger.
112
113First, import and run the `debug.attach()` function:
114
115```python
116class HomeView(TemplateView):
117 template_name = "home.html"
118
119 def get_template_context(self):
120 context = super().get_template_context()
121
122 # Make sure the debugger is attached (will need to be if runserver reloads)
123 from plain.dev import debug; debug.attach()
124
125 # Add a breakpoint (or use the gutter in VS Code to add one)
126 breakpoint()
127
128 return context
129```
130
131When you load the page, you'll see "Waiting for debugger to attach...".
132
133You can then run the VS Code debugger and attach to an existing Python process, at localhost:5678.
134
135## Installation
136
137Install the `plain.dev` package from [PyPI](https://pypi.org/project/plain.dev/):
138
139```bash
140uv add plain.dev --dev
141```
142
143Note: The `plain.dev` package does not need to be added to `INSTALLED_PACKAGES`.