Self-host Blop Cloud
Run Blop Cloud on one server of your own with Docker Compose: the app, Postgres and MinIO for artifacts.
Prerequisites
Section titled “Prerequisites”- A Linux server with Docker Engine and Docker Compose v2. The app container uses host networking.
- A public domain and a TLS-terminating proxy that forwards to port 3001 on the server. Agent connections need public HTTPS.
- The proxy appends
X-Forwarded-Forexactly once: the Compose file trusts one hop (XFF_DEPTH=1) for rate limits and audit records. - A checkout of the blop-app repository at the release you deploy.
- Secrets generated by your secret manager. Never commit them.
Start Blop Cloud
Section titled “Start Blop Cloud”-
Write the required variables to
.envin the repository root, readable only by the deploying user. -
Build and start:
Terminal window docker compose -f docker-compose.dokploy.yml up -d --buildThe app applies pending database migrations, then listens on port 3001. Postgres (5433) and MinIO (9000, 9001) are published on
127.0.0.1only. -
Open your public address and sign up.
On Dokploy, create a Compose service (type “Docker Compose”, not Swarm) pointed at docker-compose.dokploy.yml, and set the same variables in Dokploy.
The root docker-compose.yml is local development infrastructure only. To run the app from source for development, see the repository notes.
Required environment variables
Section titled “Required environment variables”| Variable | Purpose |
|---|---|
BASE_URL | Public HTTPS origin, for example https://blop.example.com, with no path. Agents connect to ${BASE_URL}/api/mcp. |
AUTH_SECRET | Signs session cookies. Changing it signs everyone out. |
PROJECT_SECRETS_KEY | Encrypts saved credentials. Changing it makes them unreadable. |
CRON_SECRET | Bearer token for the scheduled workers. |
BLOP_OAUTH_SIGNING_KEYS | Private RSA JWK set ({"keys":[…]}), each key with its own kid, for agent connections. |
BLOP_OAUTH_COOKIE_SECRET | At least 32 random characters, distinct from every other secret. |
PG_PASSWORD | Postgres password. |
MINIO_USER, MINIO_PASSWORD | MinIO credentials for the artifact bucket. |
Set OPENROUTER_API_KEY for in-app chat and browser checks started from Blop Cloud, and the GITHUB_APP_* variables for GitHub. Keep every secret stable across upgrades. Agent connections work only when BASE_URL and both OAuth variables are set. The Compose file passes only the variables it lists to the app: for Slack, Stripe, email, Sentry and other integrations, add each variable to the web-sk service’s environment: block too. Blop Cloud environment variables lists the optional ones.
Schedule the workers
Section titled “Schedule the workers”Call each endpoint on your public address with Authorization: Bearer $CRON_SECRET, from cron, a systemd timer or any scheduler:
| Endpoint | Schedule |
|---|---|
/api/scheduler/tick, /api/internal/healer/worker | Every minute |
/api/internal/management/tick, /api/internal/integrations/deliveries/worker, /api/internal/integrations/alerts/worker | POST every minute |
/api/internal/linear/sync/worker, /api/internal/jira/sync/worker | Every minute, if you use them |
/api/internal/slack/digest/worker | Hourly |
/api/internal/artifacts/cleanup | Daily; deletes artifacts after 30 days by default (ARTIFACT_RETENTION_DAYS) |
Keep the deployment safe
Section titled “Keep the deployment safe”- Run one app instance. Each workspace’s organization memory has a single writer; never scale the service or share its volume.
- Keep the named volumes
pg_data,minio_data,memory_dataandmodel_cacheacross upgrades. Never rundocker compose down -v. Volumes are not backups. - The mounted Docker socket lets the app start browser containers for check runs, and gives it control of the host’s Docker. Remove that mount if you do not run browser checks from Blop Cloud.
- Point your load balancer at
GET /api/healthz(process up) andGET /api/readyz(database and bucket reachable; 503 otherwise). BLOP_PLUGINS_SAFE_MODE=1starts only sign-in, permissions, health and machine endpoints, to recover from a bad configuration.
Upgrade Blop Cloud
Section titled “Upgrade Blop Cloud”There are no down migrations, so back up before every upgrade. Run these commands from the repository root on the server.
-
Pause your scheduler.
-
Stop the app, so nothing writes to memory:
Terminal window docker compose -f docker-compose.dokploy.yml stop web-sk -
Back up Postgres and every file in the memory volume, including
-waland-shmfiles, in this same stopped window:Terminal window B=backup/$(date +%F)mkdir -p -m 700 "$B"docker compose -f docker-compose.dokploy.yml exec -T postgres \pg_dump -U blop -d blop --format=custom > "$B/postgres.dump"docker compose -f docker-compose.dokploy.yml run --rm --no-deps -T \--entrypoint sh -v "$PWD/$B:/backup" web-sk \-c 'mkdir -p /backup/graphs && cp -a /var/lib/blop/memory/. /backup/graphs/'Never copy memory files while the app runs. Encrypt the backup and keep it off the server.
-
Check out the new release.
-
Start the new release;
--buildrebuilds the image first:Terminal window docker compose -f docker-compose.dokploy.yml up -d --build web-sk
Then check /api/readyz, sign-in, a recent check run and organization memory, and resume the scheduler.
Roll back to a backup
Section titled “Roll back to a backup”Restore into a new Compose project, so Postgres and memory start from fresh, empty volumes. Never copy restored files over existing memory files. Keep the old project’s volumes until the restore checks out.
-
Check out the release the backup was taken with.
-
Stop the current project, keeping its volumes:
Terminal window docker compose -f docker-compose.dokploy.yml stop -
Start only Postgres in a new project:
Terminal window docker compose -p blop-restore -f docker-compose.dokploy.yml up -d --wait postgres -
Restore the database into its empty
blopdatabase. The dump references theblop_analyticsrole, which a fresh Postgres lacks, so create it first:Terminal window B=backup/2026-11-03 # the backup to restoredocker compose -p blop-restore -f docker-compose.dokploy.yml exec -T postgres \psql -U blop -d blop -c 'CREATE ROLE blop_analytics NOLOGIN'docker compose -p blop-restore -f docker-compose.dokploy.yml exec -T postgres \pg_restore -U blop -d blop --exit-on-error < "$B/postgres.dump" -
Copy the memory files into the new, empty volume; the command refuses a volume that already holds files:
Terminal window B=backup/2026-11-03 # the same backup as step 4docker compose -p blop-restore -f docker-compose.dokploy.yml run --rm --no-deps -T \--entrypoint sh -v "$PWD/$B:/backup:ro" web-sk \-c 'test -z "$(ls -A /var/lib/blop/memory)" && cp -a /backup/graphs/. /var/lib/blop/memory/' -
Start the restored project, and pass
-p blop-restoreto every later command:Terminal window docker compose -p blop-restore -f docker-compose.dokploy.yml up -d --build
Artifacts in MinIO are not part of this backup, so the restored project’s MinIO starts empty. For checksums and per-file validation, use the checkpointed backup in the operator recipe, which uses the same graphs/ layout and also covers memory_migration_required.
Check it worked
Section titled “Check it worked”curl -fsS https://blop.example.com/api/readyzIt returns 200. Then sign in and connect an agent.