Configure your app with pyvolt.toml
Everything on your app's Settings tab (the serve command, release steps, Python
version, build step, background workers, and more) can also live in a
pyvolt.toml file at the root of your repo. Commit it, and your deploy config
is versioned with your code: reviewed in the same pull request, rolled back
with the same revert, and reproducible on any environment.
This is entirely optional. If your repo has no pyvolt.toml, nothing changes:
your app keeps using the settings from the dashboard exactly as before.
A complete example
[app]
serve = "gunicorn config.wsgi:application --workers 2 --bind $SOCK"
release = ["python manage.py migrate --noinput"]
release_env = true # source the app's env vars into release commands
python = "3.12" # optional, see "Versions" below
[build]
command = "npm ci && npm run build"
node = "20" # optional, see "Versions" below
[persist]
dirs = ["data"] # paths that must survive a deploy
[processes]
worker = "celery -A config worker"
beat = "celery -A config beat"
[schedule]
cleanup = { command = "python manage.py clearsessions", cron = "0 3 * * *" }
[frontend]
dir = "dist"
passthrough = ["/api", "/admin"]
[env]
required = ["SECRET_KEY"] # names only, never values
Only include the sections you need. A file with just [app] is perfectly
valid.
How it takes over
When the file is present, the keys it sets override the dashboard on every deploy, and those fields show up read-only on the Settings tab with a "Managed by pyvolt.toml" badge. The rule is per key:
- A key that is in the file is owned by the file. Edit it in the repo.
- A key that is not in the file keeps working from the dashboard as usual.
So a file that only sets [app] leaves your Processes and Frontend settings
fully editable in the dashboard. Remove a key from the file, deploy, and that
field hands control back to the dashboard.
The file never writes secrets. [env] required lists variable names only;
the values still live in your app's environment (Settings, or pyvolt env).
The sections
[app]
| Key | What it does |
|---|---|
serve |
The command that runs your web process. Must bind the socket: include --bind $SOCK (gunicorn) or --uds $SOCK_PATH (uvicorn). $APP_MODULE is also substituted. |
release |
A list of commands run on every deploy after the build and before traffic switches over (migrations, collectstatic). Runs in the project dir with your venv on PATH. |
release_env |
true to source the app's environment variables into each release command, so migrate can read DATABASE_URL. Defaults to false: release steps run with a minimal environment. |
python |
The Python version, for example "3.12". Optional, see "Versions". |
[build]
| Key | What it does |
|---|---|
command |
A build step run after dependencies install (for example a front-end build). |
node |
The Node version to install for the build, for example "20". Optional, see "Versions". |
[persist]
dirs is a list of paths, relative to your project, that must survive a
deploy. Each is stored outside the versioned release and symlinked back in, so
a SQLite database or an uploads folder is not wiped when the next release
replaces the checkout. Use it for anything your app writes to disk at runtime.
[processes]
A table of name = "command" background workers. Each becomes a supervised
service that restarts on crash, on reboot, and on every deploy. When this
section is present it defines the complete set of workers: the Processes
tab goes read-only, and any worker not listed here (including ones created in
the dashboard) is removed on the next deploy, so the file and the running set
always match. It's one or the other per app: manage processes in the dashboard,
or in the file, not both.
[schedule]
A table of name = { command = "...", cron = "..." } scheduled
jobs. cron is a 5-field expression, validated with the same
rules as the Scheduler tab. Either TOML shape works:
[schedule]
cleanup = { command = "python manage.py clearsessions", cron = "0 3 * * *" }
[schedule.weekly-report]
command = "python manage.py send_report"
cron = "0 8 * * 1"
Same complete-set rule as [processes]: when the section is present it
defines all of the app's scheduled jobs, the Scheduler tab goes read-only
(Run now and View output still work), and any job not listed - including
ones created in the dashboard - is removed on the next deploy.
[frontend]
dir is a built static/SPA directory served at the site root, and
passthrough is a list of path prefixes (each starting with /) that still
proxy to your app. With no [frontend] section, everything proxies to the app.
[env]
required is a list of environment-variable names your app must have. If any
is missing, the deploy stops immediately with a clear message, before anything
is built. This catches the classic "forgot to set SECRET_KEY" mistake early
rather than at runtime.
Versions
python and node are optional. Leave a version out (or omit the key
entirely) and the app keeps its current setting:
-
No
pythonuses the app's Python version, which defaults to 3.12 for a new app. -
No
nodeuses the repo's own Node version files (.node-version,.mise.toml) if present, otherwise the server's default Node.
Set them only when you want to pin an exact version from the repo.
Validate before you commit
The deploy validates the file first and stops on any problem: a syntax error, a
key that is not part of the schema (a typo like serv =), or a value of the
wrong shape (release must be a list, python must look like a version). The
error names the exact problem so you can fix it in one pass.
You can run that same check locally with the CLI before you commit:
pyvolt config validate # checks ./pyvolt.toml
Starting from your current settings
To turn an app you already configured in the dashboard into a file, export its current settings and commit the result:
pyvolt apps export-config my-app --write # writes ./pyvolt.toml
Edit the generated file, commit it, and your next deploy runs from the repo.
View this page as markdown - handy as context for your AI tools. Full index at /llms.txt.