OpenSEO self-hosting supports both Docker (local and self-hosted) and Cloudflare deployments; Docker uses local environment files and ephemeral storage or volumes, while Cloudflare self-hosting deploys via Alchemy with stage-specific provisioning and never reads local resource IDs. Self-hosted instances run with authentication disabled by default and operate without tier gating; they collect anonymized telemetry (heartbeats with feature counts, no user data or URLs) that can be disabled via environment flags.
All Cloudflare deployments — previews, prod, and self-host — go through Alchemy (alchemy.run.ts), which provisions real resources per stage and never reads the wrangler.jsonc resource IDs; wrangler.jsonc serves local dev and Docker self-host only.[1] The Cloudflare self-host deployment uses pnpm deploy:selfhost, which runs a preflight check, builds in selfhost mode, type-checks, then deploys with Alchemy using .env.selfhost and the selfhost stage.[2] DATAFORSEO_API_KEY is placed in .env for Docker self-hosting, in .env.selfhost for Cloudflare self-hosting, and in .env.local for local development.[3] Alchemy is a code-first infrastructure-provisioning tool for Cloudflare that creates and wires real resources (KV namespaces, Workers, etc.) per deployment stage from alchemy.run.ts, replacing the role wrangler.jsonc plays in local development.
Docker self-hosting runs with AUTH_MODE=local_noauth, disabling all auth checks and using a local admin user admin@localhost; the docs explicitly warn to only expose it behind an auth-protected reverse proxy, tunnel, or private network.[4] For Docker self-hosting, DATAFORSEO_API_KEY must be the base64-encoded value of email:password — the DataForSEO email and API password concatenated with a colon.[4] To build and run a local Docker image from source changes, use docker build -f Dockerfile.selfhost -t open-seo:local . then OPEN_SEO_IMAGE=open-seo:local docker compose up -d.[4] The self-hosted Docker instance health can be checked at /api/health, which reports configuration and database status; docker compose ps reports container health and docker compose logs shows startup checks.[4]
The KV namespace IDs in wrangler.jsonc (KV and OAUTH_KV) must NOT be changed: miniflare (and the Docker self-host persistent volume) derives its on-disk storage filenames from an HMAC of the id, so changing them orphans existing local and self-hosted databases.[1] Miniflare is the local Cloudflare Workers runtime that emulates Workers APIs — including KV storage — on the local machine; it is used during development and Docker self-hosting.
OpenSEO Docker self-hosting collects anonymized telemetry: heartbeats with aggregate counts (installs, users, projects, feature usage) tied to a random install ID, sent every 5 minutes during the first two hours after install, then at most once daily. No URLs, keywords, prompts, emails, or IP-derived location are collected. To disable, set OPENSEO_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1.[4] src/server.ts fires maybeSendSelfHostHeartbeat() on every request using ctx.waitUntil, so telemetry does not block the response.[5]
resolveAuditLimitTier in AuditService.ts always returns 'self_hosted' without consulting billing when the server is not in hosted auth mode, meaning self-hosted deployments are never gated by plan tier.[6] For self-hosted OpenSEO deployments, GSC tools check for GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and BETTER_AUTH_SECRET before attempting a token lookup; missing config returns a setup nudge with a link to the setup docs rather than a token error.[7] The missingSelfHostedGoogleClientResponse helper checks both isHostedServerAuthMode() and hasSelfHostedGoogleOAuthConfig() in parallel; only when neither condition is true does it return the setup-nudge response.[7]
Sources