# 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. ```text 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) ```bash docker --version docker compose version ``` ## First-Time Setup Clone the repo onto the host at a stable path: ```bash git clone /opt/daybook cd /opt/daybook ``` Create the environment file: ```bash cp deploy/env.example deploy/.env ``` Generate a secret key: ```bash python3 -c "import secrets; print('DJANGO_SECRET_KEY=' + secrets.token_urlsafe(50))" ``` Edit `deploy/.env` and set at minimum: ```text DJANGO_DEBUG=0 DJANGO_SECRET_KEY= DJANGO_ALLOWED_HOSTS=,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: ```bash 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: ```bash docker compose -f deploy/docker-compose.yml exec agentfeed python manage.py createsuperuser ``` ## Verify Deployment ```bash 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://: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: ```bash curl -fsS -X POST http://127.0.0.1:8000/api/artifacts \ -H "Authorization: Bearer " \ -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: ```bash 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 ```bash 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: ```bash 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: ```bash 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: ```bash 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://: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. ```text 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: ```text 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.