# Production overview DevPlace runs on a single host: two containers and one set of files shared with the development server. See also [Deploy and update](/docs/production-deploy.html), [nginx and networking](/docs/production-nginx.html), and [Multi-worker and concurrency](/docs/production-concurrency.html). ## Topology Production is Docker Compose on a single host: - **app** - Uvicorn running `devplacepy.main:app` with 2 workers (`--proxy-headers`, `--forwarded-allow-ips '*'`). Not published directly; only reachable on the internal `appnet` network as `app:10500`. - **nginx** - the public front door. It terminates HTTP on the host `PORT` (default 10500), serves static assets and uploads from disk, forces downloads on user uploads, proxies the application and the Devii WebSocket, and optionally micro-caches GETs. There is no separate database service: the data lives in SQLite files on the host. ## Shared database and files The defining property of the setup: **production uses the same database and shares as the development server.** The app container bind-mounts the host project directory (`./` to `/app`) and runs as the host user (`DEVPLACE_UID:DEVPLACE_GID`, default `1000:1000`). Every runtime artifact lives under one consolidated `data/` directory (`DEVPLACE_DATA_DIR`, default `<repo>/data`), resolved to an absolute path in `config.py`, so with no `DEVPLACE_DATABASE_URL` override the container reads and writes the **same `data/devplace.db`** as `make dev`. The bind mount also shares: - `data/devplace.db` (plus its `-wal` / `-shm` companions) - `data/uploads/` (user attachments and project files) - `data/devii_lessons.db`, `data/devii_tasks.db` (Devii memory and tasks) - `data/keys/notification-*.pem` (VAPID push keys) - `data/locks/devplace-services.lock` (background-service coordination) Because the container runs as the host user, every file it writes stays owned by the developer account, with no permission conflicts. ## Why this is safe - **Concurrency.** SQLite runs in WAL mode with a 30s busy timeout, which allows concurrent readers and a serialized writer. The dev process and the container workers can use the file at the same time. - **One services owner.** The background services (news, bots, Devii hub) are guarded by an exclusive `fcntl.flock` on `data/locks/devplace-services.lock`. Across every process that shares that file - dev and prod alike - exactly one acquires the lock and runs the services; the others skip them, so news is never fetched twice and bots never double-run. - **One DB, one truth.** No `DEVPLACE_DATABASE_URL` override means nothing can silently diverge to a second database. ## Same-host requirement SQLite is a local-file database; it cannot be shared across machines. For production and development to share one `data/devplace.db`, they must run on the **same host and filesystem**. A second server would need its own database and a different architecture (this deployment intentionally does not do that). ## Code updates without rebuild Because the host project directory is bind-mounted over `/app`, the container runs the host's current source. A code update is `git pull` followed by a restart - no image rebuild. The image is rebuilt only when Python dependencies in `pyproject.toml` change, since those are installed into the image at build time. See [Deploy and update](/docs/production-deploy.html).