# 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 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.