daybook/docs/deployment.md
peter 19c1798f90 Release v0.2.0: multiple named API keys per user
Replaces the single per-user DRF token with named, individually-revocable
API keys, so you can run one key per agent and cut off a leaked one without
disrupting the others.

- New ApiKey model (user, name, dbk_-prefixed key, created_at, last_used_at).
  Data migration copies every existing authtoken 1:1 into a "default" named
  key with the same value, so no client (incl. the sibling test project's
  hardcoded test-user key) needs reconfiguring.
- Auth (resolve_token_user) resolves against ApiKey and stamps last_used_at,
  throttled to one write/minute.
- Settings -> API Keys: table with name/key/created/last-used + per-row
  revoke and a create form; keys viewable on demand. Settings -> Account now
  links here; Settings -> Users issues a named initial key.
- Gated provisioning API for test installations: GET/POST /api/keys and
  DELETE /api/keys/{id}, controlled by DAYBOOK_KEY_PROVISIONING_API (unset ->
  follows DEBUG, so off in deployment). Advertised via key_provisioning_api
  in /api/capabilities; 403 when disabled.
- Docs (api/deployment/quickstart/posting-helper), SKILL.md, and env.example
  updated from "token / Settings -> Account" to "key / Settings -> API Keys".
- Tests: new API + UI suites for keys; existing token-based tests moved to
  ApiKey. Full suite 129 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 10:52:43 +01:00

6.4 KiB

Deployment

Daybook deploys the same way on any Linux host with Docker: a cloud VM, a home server, a Proxmox guest, whatever. It runs as a single Docker container (Gunicorn) with all persistent state — the SQLite database and artifact files — on a bind-mounted ./data directory outside the image.

Target host (cloud VM, home server, ...)
  Docker Engine
    daybook container
      /app     application code baked into the image
      /data    persistent volume: db.sqlite3, artifacts_store/

Files In deploy/

File Purpose
Dockerfile Builds the Daybook image.
docker-compose.yml Runs Daybook with a persistent ./data:/data bind mount.
entrypoint.sh Runs migrations, collects static files, then starts Gunicorn.
env.example Template for deployment environment variables.

Prerequisites On The Target Host

  • Docker Engine + the Docker Compose plugin
  • Git (for the clone-and-pull workflow below)
docker --version
docker compose version

First-Time Setup

Clone the repo onto the host at a stable path:

git clone <REPO_URL> /opt/daybook
cd /opt/daybook

Create the environment file:

cp deploy/env.example deploy/.env

Generate a secret key:

python3 -c "import secrets; print('DJANGO_SECRET_KEY=' + secrets.token_urlsafe(50))"

Edit deploy/.env and set at minimum:

DJANGO_DEBUG=0
DJANGO_SECRET_KEY=<generated-secret>
DJANGO_ALLOWED_HOSTS=<vm-ip-or-dns-name>,localhost,127.0.0.1
DAYBOOK_DATA_DIR=/data
DATABASE_PATH=/data/db.sqlite3
ARTIFACTS_ROOT=/data/artifacts_store

There is no shared API token to configure — Daybook uses per-user API keys created under Settings → API Keys once a user exists (each user can hold several, one per agent, each individually revocable). Do not commit deploy/.env.

Create the persistent data directory and bring the stack up:

mkdir -p deploy/data
docker compose -f deploy/docker-compose.yml up -d --build

Create your first login (superuser) so you have something to log in with and a personal API token to post through:

docker compose -f deploy/docker-compose.yml exec daybook python manage.py createsuperuser

Verify Deployment

docker compose -f deploy/docker-compose.yml ps
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/api/capabilities   # expect 403

An unauthenticated capabilities check returns 403 (login/token required) — that's expected; it confirms the app is up and enforcing auth. (Don't add curl -f — it treats the 403 as an error. The container healthcheck uses the login page, which is 200 without auth.) Open http://<vm-ip>:8000/ in a browser and log in with the superuser you just created, then create a personal key under Settings → API Keys and use it for a smoke-test post:

curl -fsS -X POST http://127.0.0.1:8000/api/artifacts \
  -H "Authorization: Bearer <your-personal-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "deploy-smoke",
    "kind": "markdown",
    "title": "Deployment Smoke Test",
    "content": "# Deployment Smoke Test\n\nDaybook accepted an artifact."
  }'

Fast Redeploys

Once the host has a git clone, shipping a new version is two commands:

cd /opt/daybook
git pull
docker compose -f deploy/docker-compose.yml up -d --build

The entrypoint runs Django migrations on every start, so schema changes are picked up automatically. deploy/.env and deploy/data/ are untouched by git pull since they aren't tracked in the repo.

Operational Commands

docker compose -f deploy/docker-compose.yml logs -f daybook
docker compose -f deploy/docker-compose.yml restart daybook
docker compose -f deploy/docker-compose.yml down
docker compose -f deploy/docker-compose.yml up -d
docker compose -f deploy/docker-compose.yml exec daybook python manage.py check
docker compose -f deploy/docker-compose.yml exec daybook sh

Backup And Restore

Back up deploy/data/ — it contains the SQLite database and all artifact files:

tar -czf daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data

For a consistent snapshot during heavy writes, stop the container first:

docker compose -f deploy/docker-compose.yml down
tar -czf daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data
docker compose -f deploy/docker-compose.yml up -d

To restore: stop Daybook, replace deploy/data/ with the backup contents, then start it again.

Reverse Proxy Notes

If putting Daybook behind Nginx, Caddy, Traefik, or a cloud load balancer:

  • Proxy to http://<vm-ip>:8000.
  • Add the public hostname to DJANGO_ALLOWED_HOSTS.
  • Terminate TLS at the proxy; keep DJANGO_DEBUG=0.
  • Set DJANGO_CSRF_TRUSTED_ORIGINS to the public HTTPS origin, and configure the proxy to forward X-Forwarded-Proto: https. Without both, login and any other POST fails with "CSRF verification failed" — Django has no way to know the original request was HTTPS once the proxy terminates TLS and forwards plain HTTP to the container.
DJANGO_ALLOWED_HOSTS=daybook.example.com,localhost,127.0.0.1
DJANGO_CSRF_TRUSTED_ORIGINS=https://daybook.example.com

Forwarding the proto header, by proxy:

Nginx:   proxy_set_header X-Forwarded-Proto $scheme;
Apache:  RequestHeader set X-Forwarded-Proto "https"   (requires a2enmod headers)
Caddy:   header_up X-Forwarded-Proto https

Deployment Checklist

  • deploy/.env exists with a real (non-placeholder) DJANGO_SECRET_KEY.
  • DJANGO_DEBUG=0.
  • DJANGO_ALLOWED_HOSTS includes the hostname/IP being used.
  • If behind a reverse proxy: DJANGO_CSRF_TRUSTED_ORIGINS is set and the proxy forwards X-Forwarded-Proto: https (login will 403 otherwise).
  • deploy/data/ exists and persists across git pull + rebuild.
  • docker compose -f deploy/docker-compose.yml ps shows daybook healthy.
  • GET /api/capabilities returns 403 without auth, 200 with a valid token or session (check the status code — don't use curl -f, which errors on 403).
  • At least one superuser exists (manage.py createsuperuser) with a key from Settings → API Keys.

Known MVP Constraints

  • SQLite is used by default — fine for a single-VM self-hosted deployment.
  • Per-user ingest tokens; no shared/global token.
  • Reader/browser access requires login; feeds are private per user.
  • Manual drag/drop upload and content revisions are deferred.