The app-side fix (CSRF_TRUSTED_ORIGINS, SECURE_PROXY_SSL_HEADER in
settings.py) was already committed as d458cd3 after the first Cloud-VM-1
deployment hit "CSRF verification failed" on login behind Apache. This
finishes the rest of that issue's checklist: documents
DJANGO_CSRF_TRUSTED_ORIGINS in deploy/env.example, adds the required
X-Forwarded-Proto header to the Apache/Caddy examples in
cloud_agent_deploy.md (plus a checklist item that verifies via an actual
login, not just an unauthenticated capabilities curl), and adds the same
proxy-agnostic guidance to docs/deployment.md.
Also introduces issues/, a tracked one-file-per-issue log for the project
going forward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.1 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 tokens
(DRF Token model) issued from Settings → Account once a user exists. 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 -fsS http://127.0.0.1:8000/api/capabilities
An unauthenticated capabilities check returns 403 (login/token required) —
that's expected; it confirms the app is up and enforcing auth. Open
http://<vm-ip>:8000/ in a browser and log in with the superuser you just
created, then grab a personal token from Settings → Account 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_ORIGINSto the public HTTPS origin, and configure the proxy to forwardX-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/.envexists with a real (non-placeholder)DJANGO_SECRET_KEY.DJANGO_DEBUG=0.DJANGO_ALLOWED_HOSTSincludes the hostname/IP being used.- If behind a reverse proxy:
DJANGO_CSRF_TRUSTED_ORIGINSis set and the proxy forwardsX-Forwarded-Proto: https(login will 403 otherwise). deploy/data/exists and persists acrossgit pull+ rebuild.docker compose -f deploy/docker-compose.yml psshowsdaybookhealthy.curl -fsS http://127.0.0.1:8000/api/capabilitiesreturns 403 without auth, 200 with a valid token or session.- At least one superuser exists (
manage.py createsuperuser) with a token from Settings → Account.
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.