Skip to content

Self-host Blop Cloud

Run Blop Cloud on one server of your own with Docker Compose: the app, Postgres and MinIO for artifacts.

  • 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-For exactly 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.
  1. Write the required variables to .env in the repository root, readable only by the deploying user.

  2. Build and start:

    Terminal window
    docker compose -f docker-compose.dokploy.yml up -d --build

    The app applies pending database migrations, then listens on port 3001. Postgres (5433) and MinIO (9000, 9001) are published on 127.0.0.1 only.

  3. 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.

VariablePurpose
BASE_URLPublic HTTPS origin, for example https://blop.example.com, with no path. Agents connect to ${BASE_URL}/api/mcp.
AUTH_SECRETSigns session cookies. Changing it signs everyone out.
PROJECT_SECRETS_KEYEncrypts saved credentials. Changing it makes them unreadable.
CRON_SECRETBearer token for the scheduled workers.
BLOP_OAUTH_SIGNING_KEYSPrivate RSA JWK set ({"keys":[…]}), each key with its own kid, for agent connections.
BLOP_OAUTH_COOKIE_SECRETAt least 32 random characters, distinct from every other secret.
PG_PASSWORDPostgres password.
MINIO_USER, MINIO_PASSWORDMinIO 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.

Call each endpoint on your public address with Authorization: Bearer $CRON_SECRET, from cron, a systemd timer or any scheduler:

EndpointSchedule
/api/scheduler/tick, /api/internal/healer/workerEvery minute
/api/internal/management/tick, /api/internal/integrations/deliveries/worker, /api/internal/integrations/alerts/workerPOST every minute
/api/internal/linear/sync/worker, /api/internal/jira/sync/workerEvery minute, if you use them
/api/internal/slack/digest/workerHourly
/api/internal/artifacts/cleanupDaily; deletes artifacts after 30 days by default (ARTIFACT_RETENTION_DAYS)
  • 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_data and model_cache across upgrades. Never run docker 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) and GET /api/readyz (database and bucket reachable; 503 otherwise).
  • BLOP_PLUGINS_SAFE_MODE=1 starts only sign-in, permissions, health and machine endpoints, to recover from a bad configuration.

There are no down migrations, so back up before every upgrade. Run these commands from the repository root on the server.

  1. Pause your scheduler.

  2. Stop the app, so nothing writes to memory:

    Terminal window
    docker compose -f docker-compose.dokploy.yml stop web-sk
  3. Back up Postgres and every file in the memory volume, including -wal and -shm files, 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.

  4. Check out the new release.

  5. Start the new release; --build rebuilds 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.

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.

  1. Check out the release the backup was taken with.

  2. Stop the current project, keeping its volumes:

    Terminal window
    docker compose -f docker-compose.dokploy.yml stop
  3. Start only Postgres in a new project:

    Terminal window
    docker compose -p blop-restore -f docker-compose.dokploy.yml up -d --wait postgres
  4. Restore the database into its empty blop database. The dump references the blop_analytics role, which a fresh Postgres lacks, so create it first:

    Terminal window
    B=backup/2026-11-03 # the backup to restore
    docker 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"
  5. 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 4
    docker 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/'
  6. Start the restored project, and pass -p blop-restore to 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.

Terminal window
curl -fsS https://blop.example.com/api/readyz

It returns 200. Then sign in and connect an agent.