Self-contained operational SOP for an autonomous agent deploying and operating Daybook on a cloud VM: bootstrapping its own scoped SSH deploy key, cloud-specific network/firewall setup, the git-pull redeploy loop with rollback, TLS hardening for internet-facing hosts, and a completion checklist. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.7 KiB
Cloud Deployment Runbook For A Dedicated Deploy Agent
This document is written for an autonomous AI agent responsible for deploying and operating Daybook on a cloud VM, without a human walking through each step. Follow it in order. Where a decision depends on information you don't have (VM IP, domain, whether TLS is required), ask the operator once, then proceed autonomously for routine redeploys.
For background on the app itself and the general (human-facing) version of
this workflow, see docs/deployment.md. This file is the operational SOP;
that one is the reference.
Source Of Truth
The canonical source is a private Forgejo repository:
https://git.cygnus.systems/peter/daybook.git
Do not deploy from a copied/rsynced tree. Always deploy from a git clone of
this repository so git pull is available for fast redeploys.
0. Get Repo Access
You need read access to a private repo. Prefer a repo-scoped SSH deploy key over a personal access token — it can only read this one repo, unlike a token tied to a full user account.
- Generate an SSH key pair on the VM (or ask the operator for one to install):
ssh-keygen -t ed25519 -C "daybook-deploy-agent" -f ~/.ssh/daybook_deploy -N "" - Ask the operator to add the public key (
~/.ssh/daybook_deploy.pub) as a Deploy Key on the repo in Forgejo (repo → Settings → Deploy Keys → read-only is sufficient), or hand it to them directly — never ask them to give you their personal account credentials. - Configure SSH to use it for this host:
cat >> ~/.ssh/config <<'EOF' Host git.cygnus.systems IdentityFile ~/.ssh/daybook_deploy IdentitiesOnly yes EOF - Verify:
ssh -T git@git.cygnus.systems
If SSH access can't be arranged, fall back to an HTTPS access token the
operator generates (Forgejo → user Settings → Applications → Generate New
Token, read-only repo scope) and clone with:
git clone https://<username>:<token>@git.cygnus.systems/peter/daybook.git /opt/daybook
1. Prerequisites On The VM
Confirm before proceeding; install if missing:
docker --version || curl -fsSL https://get.docker.com | sh
docker compose version
git --version
Confirm outbound network access to pull the Python base image and packages,
and confirm you know the VM's public IP or DNS name — you'll need it for
DJANGO_ALLOWED_HOSTS and for reporting the final URL back.
2. Clone
sudo mkdir -p /opt/daybook
sudo chown "$USER:$USER" /opt/daybook
git clone git@git.cygnus.systems:peter/daybook.git /opt/daybook
cd /opt/daybook
3. Configure Environment
cp deploy/env.example deploy/.env
Generate a real secret key — do not deploy with the placeholder:
python3 -c "import secrets; print('DJANGO_SECRET_KEY=' + secrets.token_urlsafe(50))"
Edit deploy/.env:
DJANGO_DEBUG=0
DJANGO_SECRET_KEY=<generated-secret>
DJANGO_ALLOWED_HOSTS=<vm-public-ip>,<domain-if-any>,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 set — Daybook issues a personal token per
user from Settings → Account once a user exists (step 5). Never commit
deploy/.env; it is already gitignored.
4. Network Access
Cloud VMs default to a locked-down security group/firewall, unlike a home LAN. Before starting the stack, open the port you'll serve on:
- If exposing Daybook directly: open TCP
8000to the addresses that should reach it (ideally not0.0.0.0/0unless this is meant to be public). - If putting a reverse proxy in front (recommended for anything
internet-facing — see §8): open
80/443instead, and keep8000closed to everything exceptlocalhost.
This is provider-specific (AWS security group, GCP firewall rule, DigitalOcean
cloud firewall, or the VM's own ufw/iptables). Ask the operator which
provider/console to use if you don't have API access to it yourself.
5. Build, Start, Create A User
mkdir -p deploy/data
docker compose -f deploy/docker-compose.yml up -d --build
docker compose -f deploy/docker-compose.yml exec daybook python manage.py createsuperuser
The entrypoint runs migrations and collects static files automatically on every start.
6. Verify
docker compose -f deploy/docker-compose.yml ps
curl -fsS http://127.0.0.1:8000/api/capabilities
A 403 with "Login or a valid API token is required" is the expected
response when unauthenticated — it confirms the app is up. Log in at
http://<vm-ip-or-domain>:8000/ (or through your reverse proxy) with the
superuser from step 5, grab a personal token from Settings → Account, then
smoke-test a post:
curl -fsS -X POST http://127.0.0.1:8000/api/artifacts \
-H "Authorization: Bearer <token-from-settings>" \
-H "Content-Type: application/json" \
-d '{"source": "deploy-agent", "kind": "markdown", "title": "Deployment Smoke Test", "content": "# OK\n\nDeployed successfully."}'
Confirm the response includes id, url, and api_url, and that opening
url in a browser renders the artifact.
7. Redeploy Loop (routine updates)
This is the command you run every time you're asked to ship a new version:
cd /opt/daybook
PREV_SHA=$(git rev-parse HEAD)
git pull
docker compose -f deploy/docker-compose.yml up -d --build
Then repeat the verification in step 6 (capabilities check at minimum). If it fails:
git checkout "$PREV_SHA"
docker compose -f deploy/docker-compose.yml up -d --build
to roll back, then report the failure (build log / migration error / failed health check) back to the operator rather than retrying blindly.
deploy/.env and deploy/data/ are untracked, so git pull never touches
them — no need to back them up before a routine redeploy.
8. Hardening For Internet-Facing Deployments
If this VM is reachable from the public internet (not just a private network/VPN), do this before calling the deployment done:
- Put a reverse proxy (Caddy, Nginx, or the cloud provider's load balancer) in front with TLS. Bearer tokens go over plaintext HTTP otherwise.
- Add the proxy's public hostname to
DJANGO_ALLOWED_HOSTS. - Keep
DJANGO_DEBUG=0(already set above). - Restrict SSH to key-based auth only; disable password login if you have access to do so.
- Confirm the VM's firewall only allows the ports actually needed (80/443, and SSH from wherever the operator connects from).
Caddy is the lowest-effort option (automatic TLS via Let's Encrypt):
daybook.example.com {
reverse_proxy 127.0.0.1:8000
}
9. Backup
tar -czf /tmp/daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data
Copy the resulting archive off the VM (to object storage, the operator's machine, etc.) — a backup that only exists on the VM it's protecting against doesn't help if the VM is lost.
Deployment Checklist
Before reporting the deployment complete, confirm all of these:
- Cloned via git (not copied), with a working
git pull. deploy/.envhas a real, non-placeholderDJANGO_SECRET_KEY.DJANGO_ALLOWED_HOSTSincludes the actual hostname/IP being used.docker compose -f deploy/docker-compose.yml psshowsdaybookhealthy./api/capabilitiesreturns403unauthenticated,200with a token.- At least one superuser exists with a working personal API token.
- A smoke-test artifact posts and renders successfully.
- If internet-facing: TLS is in front of the app, not raw HTTP on
8000. - A backup of
deploy/data/has been taken and copied off the VM.
Report back to the operator: the app URL, confirmation of the checklist above, and the deploy SSH key fingerprint or token identity used (not the key/token itself) for their records.