|
<div class="docs-content" data-render>
|
|
# Deploy and update
|
|
|
|
First deploy, updates, environment, and rollback for the production stack. See also [Production overview](/docs/production.html) and [nginx and networking](/docs/production-nginx.html).
|
|
|
|
## Prerequisites
|
|
|
|
- Docker with the Compose plugin.
|
|
- The repository checked out on the **same host** that runs (or will run) the development server, so they share one database.
|
|
|
|
## Environment
|
|
|
|
Configuration is a single `.env` file in the project root (git-ignored), loaded both by Compose (`env_file`) and by the app at startup (`python-dotenv`). Copy the committed template and edit it.
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
| Variable | Default | Purpose |
|
|
|----------|---------|---------|
|
|
| `SECRET_KEY` | insecure placeholder | Session signing. **Change this.** |
|
|
| `DEVPLACE_DATABASE_URL` | unset | Leave unset to share `data/devplace.db`. Set only to point elsewhere. |
|
|
| `DEVPLACE_DATA_DIR` | `<repo>/data` | Single root for all runtime data (DB, uploads, keys, locks, bot state, staging, workspaces). Point at a volume in production. |
|
|
| `DEVPLACE_SITE_URL` | empty | Public origin for absolute URLs (SEO, canonical, push). Empty derives from the request. |
|
|
| `PORT` | `10500` | Host port the nginx front door binds. |
|
|
| `NGINX_MAX_BODY_SIZE` | `50m` | nginx upload ceiling. Must be >= `max_upload_size_mb`. |
|
|
| `NGINX_CACHE_ENABLED` | `false` | Enable the nginx micro-cache for proxied GETs. |
|
|
| `NGINX_CACHE_MAX_SIZE` | `1g` | Cache size cap when enabled. |
|
|
| `DEVPLACE_UID` / `DEVPLACE_GID` | `1000` | Host user the app container runs as, so shared files keep dev ownership. Match `id -u` / `id -g`. |
|
|
|
|
Operational behavior (rate limits, registration, maintenance mode, upload size, news cadence) is tuned live from **Admin -> Settings** and stored in `site_settings`; it needs no redeploy.
|
|
|
|
## First deploy
|
|
|
|
```bash
|
|
cp .env.example .env # set SECRET_KEY, PORT, DEVPLACE_SITE_URL
|
|
make docker-build # build the image (installs the docker CLI)
|
|
make docker-up # start (creates ./data, mounts the socket)
|
|
```
|
|
|
|
Open `http://<host>:<PORT>`. Useful targets: `make docker-logs` (tail), `make docker-down` (stop), `make docker-clean` (down).
|
|
|
|
The make targets always apply the `docker-compose.containers.yml` overlay, so the admin-only Container Manager works with no extra setup. Always update through them: a plain `docker compose up -d` drops the overlay and silently removes the docker socket and CLI the manager needs.
|
|
|
|
The healthcheck gives the app a 120s start window in which a failing probe does not count against `retries` (`start_period: 120s`), and probes every 2s inside that window (`start_interval: 2s`) so the container is marked healthy as soon as it actually serves rather than at the next 30s tick. Both settings live in `docker-compose.yml` and the `Dockerfile` and must be changed together. Normal startup is a few seconds; the 120s window is headroom for a cold page cache on a multi-GB database. Startup cost is paid once per uvicorn worker, serialized under an exclusive init lock, so anything added to `init_db` is multiplied by the worker count. If you add heavyweight init work, measure the real boot time before relying on the existing window.
|
|
|
|
## Updating
|
|
|
|
Code is bind-mounted, so most updates need no rebuild:
|
|
|
|
```bash
|
|
git pull
|
|
make docker-reload # restart workers on the new code
|
|
```
|
|
|
|
Use `make docker-reload`, not `make docker-up`: `docker compose up -d` sees an unchanged container and leaves it running, so the workers keep serving the code they imported at boot. `docker-reload` restarts the app and waits for it to report healthy again.
|
|
|
|
Rebuild the image only when dependencies change:
|
|
|
|
```bash
|
|
make docker-build && make docker-up
|
|
```
|
|
|
|
A rebuild after a source-only change takes about 7 seconds. Dependencies install from `pyproject.toml` in a layer that source edits cannot invalidate, so `pip install` and the Chromium download stay cached; only the source copy and the final project install re-run.
|
|
|
|
## Release branch
|
|
|
|
`make deploy` fast-forwards the release branch:
|
|
|
|
```bash
|
|
git checkout production && git merge master && git push origin production
|
|
```
|
|
|
|
On the server, pull `production` and run the update steps above. CI (`.gitea/workflows/test.yaml`) runs the full test suite on every push/PR to `master`, so only tested commits reach `production`.
|
|
|
|
## Rollback
|
|
|
|
Because the container runs host source, rolling back is a git operation:
|
|
|
|
```bash
|
|
git checkout <previous-good-commit>
|
|
make docker-up
|
|
```
|
|
|
|
Back up `data/devplace.db` (and its `-wal`/`-shm`) before a risky change; the schema auto-syncs forward but is not auto-downgraded.
|
|
|
|
## Bare-metal alternative
|
|
|
|
`make prod` runs the same app without containers (`uvicorn ... --workers $(WEB_WORKERS) --proxy-headers`, where `WEB_WORKERS` defaults to the box core count via `nproc`) from the project root, sharing the same database and files. It binds port 10500 directly, conflicting with the Docker front door on that port - run one or the other, or set a different `PORT`. Keep `DEVPLACE_WEB_WORKERS` in lockstep with `--workers`; see [Multi-worker and concurrency](/docs/production-concurrency.html) for why.
|
|
</div>
|