Removes the "Daybook" name from everything user- and agent-facing, since no agents are configured yet and this is the cheap moment to do it. - UI brand, docs, README, CLAUDE.md -> AgentFeed. - Ingest env vars DAYBOOK_URL/TOKEN/SOURCE/DATA_DIR/BIND_ADDR/HOST_DATA_DIR/ KEY_PROVISIONING_API -> AGENTFEED_* (token *values* unchanged). - Skill: skills/daybook-posting -> skills/agentfeed-posting, daybook_post.py -> agentfeed_post.py (+ scripts/ wrapper and its test). - static/daybook.css -> static/agentfeed.css; localStorage daybook.* -> agentfeed.*; templatetag daybook_extras -> agentfeed_extras; multipart boundaries; json/csv viewer element ids; daybook_exception_handler -> agentfeed_exception_handler; compose service/container -> agentfeed. - Key-provisioning API now OFF by default (independent of DEBUG); enable with AGENTFEED_KEY_PROVISIONING_API=1 on test installs only. - Posting helper now points at Settings -> API Keys. Deferred on purpose (a later, separate step): the Django package `daybook_project`, the `/opt/daybook` install path, and the Forgejo repo name. The sibling AGENT_DAYBOOK__LLM_TEST name is intentionally untouched. Full suite: 129 passing; live server verified serving under the new name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 token 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-token>" \
-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_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=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/.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 psshowsagentfeedhealthy.GET /api/capabilitiesreturns 403 without auth, 200 with a valid token or session (check the status code — don't usecurl -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.