Self-hosting
Backups and upgrades
What to back up, the encryption key, migrations and retention.
Running AgentDock day to day comes down to four things: back up PostgreSQL and the encryption key, rebuild the images to upgrade, know what is deleted on its own, and know where to look when something is unhealthy. The commands below assume the Docker Compose stack in ~/stacks/agentdock.
What to back up
| What | Where | Why |
|---|---|---|
| PostgreSQL | the pgdata volume | Everything AgentDock knows: accounts, projects, runners, history, sessions, cost, settings, audit. This is the source of truth. |
APP_ENCRYPTION_KEY | env/api.env | Decrypts the stored secrets (Telegram bot token, webhook and trigger secrets, GitHub App key). Back it up separately from the database. |
.env and env/*.env | the stack directory | Database and Redis passwords, the tunnel token, your settings. |
docker-compose.yml, Caddyfile | the stack directory | Small, but you will want them. |
Redis does not need a backup. It only holds BullMQ timers for cron schedules, PostgreSQL stays the source of truth, and a reconciler rebuilds lost timers on start.
Nothing on the agent machines needs a server-side backup either. Each runner keeps its token in ~/.config/agentdock/runner.json; if that file is lost, pair the machine again.
Dump the database
cd ~/stacks/agentdock
docker compose exec -T postgres pg_dump -U postgres -Fc agentdock > agentdock-$(date +%F).dumpRun it from cron and copy the dumps off the host. The custom format (-Fc) is compressed and restores with pg_restore.
Restore
Restore into an empty database with the API stopped, then start the API again. It applies any newer migrations on start.
cd ~/stacks/agentdock
docker compose stop api web proxy
docker compose exec -T postgres dropdb -U postgres agentdock
docker compose exec -T postgres createdb -U postgres agentdock
docker compose exec -T postgres pg_restore -U postgres -d agentdock --no-owner < agentdock-2026-10-01.dump
docker compose up -dThe restored instance needs the same APP_ENCRYPTION_KEY as the one the dump was taken from. With a different key the API starts, but every stored secret fails to decrypt and has to be entered again.
A database dump without the key does not reveal the stored secrets, and the key without the dump is useless. Keep them in different places, and make sure you can get both back.
Upgrades
There is no published image, so an upgrade is: update the checkout, rebuild both images under the same tags, and recreate the containers.
cd ~/src/AgentDock
git pull
docker build -t agentdock-api:local -f apps/api/Dockerfile .
docker build -t agentdock-web:local -f apps/web/Dockerfile \
--build-arg NEXT_PUBLIC_LIVE_URL=wss://agentdock.example.com/api/live \
--build-arg API_URL=http://api:8180 \
.
cd ~/stacks/agentdock
docker compose up -d
docker image prune -fWhat happens on start:
- The API entrypoint runs
prisma migrate deploy, thenprisma db seed, then starts the server. The seed is idempotent: it upserts settings and adds the price table only when none exists, so your price edits survive. - If a migration fails, the API refuses to start and stays unhealthy. The web app and the proxy wait for a healthy API, so the site is down until you fix it. Read
docker compose logs api. - Migrations only go forward. Take a dump before every upgrade; rolling back means restoring it and rebuilding the previous commit.
To pin a version, build from a specific commit instead of the latest one (git checkout <commit>). Compare the new .env.example files with yours after pulling: new variables are documented there.
Upgrading runners
Runners upgrade on their own machines with the tool that installed them, then restart:
brew upgrade agentdock-runner && brew services restart agentdock-runner
# or
npm i -g agentdock-runner@latest && systemctl --user restart agentdock-runnerThe runner and the API speak a versioned protocol. If they no longer match, the API closes the connection with code 4400 and the runner exits with code 78, which the service does not restart. Upgrade the side that is behind.
Retention
Some tables are cleaned up by jobs inside the API. The rest grow until you act.
| Data | Kept | Job |
|---|---|---|
| Activity feed items | 180 days (ACTIVITY_RETENTION_DAYS) | daily at 04:30 |
| Outbound webhook deliveries and inbound trigger deliveries | 30 days | daily at 04:45 |
| GitHub App webhook deliveries | 14 days | daily at 04:50 |
| BullMQ jobs in Redis | 24 hours when completed, 7 days when failed | BullMQ |
| Audit records | never deleted | none |
| Events, LLM requests, sessions, runs, issue cache | no automatic deletion | none |
The audit log is append-only by design. A database trigger rejects UPDATE, DELETE and TRUNCATE on audit_records, and every record is hash-chained to the previous one. A nightly job (AUDIT_VERIFY_CRON, default 03:00) verifies the chain, and Admin → Audit shows the result. Admin → Schedules lists these system jobs next to the projects' schedules. Job times are server time.
Plan disk space for the tables without retention. Sessions and LLM requests are the largest on a busy fleet.
Health checks
| Check | Command | Healthy answer |
|---|---|---|
| API, from inside the stack | GET http://api:8180/health | 200 with {"status":"ok","timestamp":"..."} |
| API, through the proxy | curl https://agentdock.example.com/api/health | the same |
| Web app | GET http://web:3517/ | any status below 500 (signed-out pages redirect) |
| Containers | docker compose ps | api and web show healthy |
| A runner | agentdock-runner status on its machine | paired: yes, reach: reachable |
/health is public and says only that the process is up. It does not check the database. For the fleet itself, Admin → Runners shows each runner's status with a remedy when it is offline, and admins get a runner.offline notification after a runner has been gone for more than 5 minutes.
Logs
docker compose logs -f apifor the API: migrations, workers, runner connections.docker compose logs -f webanddocker compose logs -f proxyfor the rest of the stack.- On agent machines, see Runner logs.