Deployment¶
For developers and operators only
This section is for working on PK-DB itself or running a separate server. General users do not need a source checkout, Docker, a database, or administrator access. To use PK-DB, start with Browse and access data or the Python client and API.
The root compose.yaml is a local setup with an optional dev profile for the frontend. It runs the API and PostgreSQL, applies Alembic migrations, and loads the bundled vocabulary. See Installation for startup and Local upload testing for the upload workflow.
Deployment requirements¶
For an externally accessible deployment, provide a TLS reverse proxy, managed secrets, a durable PostgreSQL database, persistent attachment storage, and an appropriate backup policy. Configure the backend with PKDB_DATABASE_URL, PKDB_FILE_ROOT, and explicit PKDB_CORS_ORIGINS when using a separately hosted frontend. Additional settings are listed in backend/src/pkdb_server/config.py. The local default database password is only for local testing. The reverse proxy section describes the nginx configuration used for beta.pk-db.com.
The container runs as UID 10001. A custom attachment mount must be writable by that user. Do not expose PostgreSQL publicly. Apply migrations once before starting API workers; the Compose startup command handles this for the single local API service.
Backup and restore¶
PostgreSQL records and attachment files form one dataset. Quiesce writes, back up both together, and retain the application version and configuration needed to restore. The local Compose volumes are database and attachments, prefixed by the project name. Never point a new deployment at historical volumes without an explicit migration.
Test restoration into an isolated database and attachment volume before relying on a backup. The backend system tests exercise database and attachment restoration together. A database dump alone does not preserve attachments.
Migration status¶
The current backend is the only backend shipped in this repository. Removing the previous implementation is not a production data migration or a claim that every historical client or source study passes acceptance. Historical validation and compatibility evidence remains in the migration records.
Frontend static artifact¶
The modern frontend uses Node 24.21.0 and npm 12.1.0 with a committed lockfile. frontend/Dockerfile-production runs npm ci, type checks, and builds dist/, then copies the static artifact to /vue. This image remains an artifact carrier; the existing deployment supplies its HTTP server. No frontend service is added to the default backend Compose stack.
The shared footer identifies the frontend release from frontend/package.json and the source commit captured at build time. Native builds detect Git HEAD automatically. Docker builds use a frontend-only context without Git metadata, so supply the full source commit explicitly from the repository root:
docker build --build-arg PKDB_BUILD_COMMIT="$(git rev-parse HEAD)" \
-f frontend/Dockerfile-production -t pkdb-frontend:local frontend
For source archives, set PKDB_BUILD_COMMIT to the full commit hash before npm run build. If no commit is available, the footer says Commit unavailable instead of linking to an incorrect revision. Rebuild the frontend artifact to update its release information.
compose.frontend-test.yaml exercises the production application configuration nginx/pkdb.conf against isolated test data. Proxy /api, /accounts, /media, /static and /health before history fallback. Missing JS/CSS assets must return 404, API failures must remain API responses, and index.html must be revalidated while fingerprinted assets may be cached immutably. Match PKDB_BROWSER_ORIGIN to the external origin and preserve same-origin cookies/CSRF. VITE_API_BASE is public API-origin build configuration, normally empty for same-origin requests, never a place for secrets.
Before release, verify these rules in the real deployment proxy and retain the previous deployed artifact in durable storage. Rollback restores that static artifact atomically; this frontend migration introduces no database migration. Local recovered baseline checksums are in frontend/docs/modernization-baseline.md; local /tmp copies do not replace production rollback retention.
Reverse proxy¶
beta.pk-db.com uses two nginx layers, both kept in nginx/:
| File | Host | Role |
|---|---|---|
nginx/beta.pk-db.com |
Gateway | TLS termination, Let's Encrypt webroot, HTTP to HTTPS redirect; forwards everything to the application host 192.168.0.176:18083 |
nginx/ssl.conf |
Gateway | Shared TLS settings, installed as /etc/nginx/snippets/ssl.conf |
nginx/pkdb.conf |
Application host | Serves the built frontend and forwards /api, /accounts, /media, /health, /mcp, /docs, /redoc and /openapi.json to the backend |
On the gateway, install the site and snippet, obtain the certificate with the webroot authenticator, and reload nginx:
sudo cp nginx/beta.pk-db.com /etc/nginx/sites-available/beta.pk-db.com
sudo cp nginx/ssl.conf /etc/nginx/snippets/ssl.conf
sudo ln -s /etc/nginx/sites-available/beta.pk-db.com /etc/nginx/sites-enabled/
sudo certbot certonly --webroot -w /usr/share/nginx/letsencrypt -d beta.pk-db.com
sudo nginx -t && sudo systemctl reload nginx
The TLS snippet expects /etc/ssl/certs/dhparam.pem (openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048). Until a certificate exists, comment out the HTTPS server block so that nginx can start and answer the ACME challenge.
On the application host, start the stack with the production override. It builds an nginx image containing the frontend, publishes it on PKDB_WEB_PORT (default 18083) on all interfaces, and stops publishing the backend port:
export PKDB_BROWSER_ORIGIN=https://beta.pk-db.com
PKDB_BUILD_COMMIT="$(git rev-parse HEAD)" \
docker compose -f compose.yaml -f compose.production.yaml up --build --wait
The override also enables secure cookies. Restrict the published port to the gateway with the host firewall.
The gateway replaces any client-supplied X-Forwarded-For header with the connecting address. The application nginx accepts that header only from 192.168.0.0/24 and passes one address to the backend, which trusts it for per-IP quotas (FORWARDED_ALLOW_IPS). Adjust set_real_ip_from in nginx/pkdb.conf if the gateway uses another address. MCP responses are streamed, so the gateway disables proxy buffering for /mcp/ and the application nginx for all backend routes.
Deployment configuration¶
Serve the frontend and backend behind one HTTPS origin. Forward /api/ and /accounts/ to FastAPI, together with any enabled MCP route. Keep the frontend API base relative (VITE_API_BASE=""). A browser origin includes the scheme, hostname and optional port, with no path.
| Environment variable | Production setting or behavior |
|---|---|
PKDB_DATABASE_URL |
PostgreSQL connection URL |
PKDB_FILE_ROOT |
Persistent managed file directory; includes avatars |
PKDB_BROWSER_ORIGIN |
Exact public origin, such as https://pk-db.example.org |
PKDB_SECURE_COOKIES |
true for HTTPS production |
PKDB_SMTP_HOST, PKDB_SMTP_SENDER |
Required to send verification, recovery and invitation mail |
PKDB_SMTP_PORT |
Defaults to 587 |
PKDB_SMTP_USERNAME, PKDB_SMTP_PASSWORD |
Optional SMTP authentication; provide both when required |
PKDB_SMTP_STARTTLS |
Defaults to true; SMTP transport uses STARTTLS, not implicit TLS |
PKDB_CORS_ORIGINS |
Explicit JSON array when needed; same-origin deployment normally needs none |
PKDB_UPLOAD_MAX_ROWS |
Table rows of one upload, study format 1 or 2; defaults to 1000000 |
PKDB_UPLOAD_MAX_BYTES, PKDB_UPLOAD_MAX_FILES |
Size and number of files of one upload; default to 256 MB and 256 |
PKDB_UPLOAD_CONCURRENCY |
Uploads processed at the same time; defaults to 2 |
Size the memory of the API workers for PKDB_UPLOAD_MAX_ROWS. Validating and preparing an upload holds its rows in memory, about 2 KB per row for study format 1 and study format 2 alike, so the default of one million rows needs about 2 GB for each concurrent upload, and the default concurrency of 2 about 4 GB. Uploads stop reading at the row limit, so the limit and not the file size bounds this memory. Lower PKDB_UPLOAD_MAX_ROWS (or PKDB_UPLOAD_CONCURRENCY) when the workers have less memory; real studies have far fewer rows.
Production cookies are Secure, HttpOnly and SameSite=Lax, with __Host- names. Insecure cookies are restricted to local development hosts. Browser mutations require the expected Origin and a CSRF token obtained from GET /api/v1/auth/csrf; the frontend handles this automatically. An invalid explicit Authorization header does not fall back to a browser cookie.
SMTP is needed for public registration verification, password recovery, and invitations. Existing active accounts with a password can sign in without SMTP. Local development accounts can be provisioned directly with the operator command below. Invitation recipients enter their one-use token at /invitation and choose a password.
Back up PostgreSQL and managed file storage together. Do not log Authorization headers, one-use tokens, passwords, or request bodies from authentication routes.