daybook/docs/deployment.md
peter fcd7da853f docs: say 'API key' instead of 'token' in agent-facing wording
Sweep human-facing 'personal API token' / '<your-personal-token>'
placeholders to 'API key' / '<your-personal-key>' across the API docs,
quickstart, deployment guide, LLM helper, and posting skill, matching the
in-app 'Settings -> API Keys' naming. The AGENTFEED_TOKEN env var name and
HTTP 'bearer token' terminology are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 23:23:53 +01:00

7.9 KiB

Deployment

AgentFeed 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
    agentfeed container
      /app     application code baked into the image
      /data    persistent volume: db.sqlite3, artifacts_store/

Files In deploy/

File Purpose
Dockerfile Builds the AgentFeed image.
docker-compose.yml Runs AgentFeed 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
AGENTFEED_DATA_DIR=/data
DATABASE_PATH=/data/db.sqlite3
ARTIFACTS_ROOT=/data/artifacts_store

There is no shared API token to configure — AgentFeed 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 key to post through:

docker compose -f deploy/docker-compose.yml exec agentfeed 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-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "deploy-smoke",
    "kind": "markdown",
    "title": "Deployment Smoke Test",
    "content": "# Deployment Smoke Test\n\nAgentFeed 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 agentfeed
docker compose -f deploy/docker-compose.yml restart agentfeed
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 agentfeed python manage.py check
docker compose -f deploy/docker-compose.yml exec agentfeed sh

Django Admin (/admin/)

AgentFeed ships Django's built-in admin at /admin/ as an operator break-glass tool — for raw-data fixes: reset a password, inspect or delete any user's artifacts, adjust a quota, audit or revoke API keys. It is separate from the in-app Settings area, which is the everyday product surface (see the in-app guide / docs/ for normal use).

Logging in: open /admin/ and sign in with an account that has is_staff = True. The account you create with createsuperuser has it (plus full superuser rights). Note the two different admin concepts:

  • is_superuser — AgentFeed's in-app admin (can reach Settings → Users).
  • is_staff — can log into Django's /admin/ site.

The createsuperuser account has both. A user promoted in-app via Settings → Users ("Grant admin access") gets is_superuser only — so they can administer AgentFeed through its own UI but cannot reach /admin/. Regular users have neither. To grant someone /admin/ access, set staff explicitly:

docker compose -f deploy/docker-compose.yml exec agentfeed \
  python manage.py shell -c "from django.contrib.auth.models import User; u=User.objects.get(username='NAME'); u.is_staff=True; u.save()"

Caution: the admin edits database rows directly and bypasses app logic — e.g. deleting an artifact there does not clean up its files on disk the way the app's own delete does. Use it for inspection and targeted fixes, not routine management.

Backup And Restore

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

tar -czf agentfeed-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 agentfeed-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 AgentFeed, replace deploy/data/ with the backup contents, then start it again.

Reverse Proxy Notes

If putting AgentFeed 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=agentfeed.example.com,localhost,127.0.0.1
DJANGO_CSRF_TRUSTED_ORIGINS=https://agentfeed.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 agentfeed 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 API keys (multiple per user, individually revocable); no shared/global key.
  • Reader/browser access requires login; feeds are private per user.
  • Manual drag/drop upload and content revisions are deferred.