Docker vs. Cloudflare Self-Hosting for OpenSEO: A Complete Comparison

OpenSEO offers two distinct self-hosting paths—Docker for containerized local deployment and Cloudflare Workers for serverless edge distribution—differing fundamentally in runtime environment, authentication model, data persistence, and operational overhead.

Both options ship the same core application from the every-app/open-seo repository, but they target different operational needs. Docker provides full control over a single-host environment, while Cloudflare delivers globally distributed infrastructure with built-in access management. This guide breaks down the architectural differences, deployment flows, and decision criteria for each approach.

Execution Environment and Runtime

The most fundamental difference lies in where and how the application code runs.

Docker: Containerized Node.js Runtime

Docker self-hosting bundles OpenSEO into a complete container image based on node:22. The Dockerfile.selfhost defines this environment, and docker-entrypoint.sh handles startup tasks including database migrations and build steps.


# Docker quick-start

cp .env.example .env                     # set DATAFORSEO_API_KEY, etc.

docker compose up -d                     # pulls ghcr.io/every-app/open-seo:latest

The container includes:

  • Full Node.js 22 runtime
  • Local filesystem access for SQLite persistence
  • Self-contained dependency installation

Cloudflare Workers: V8 Isolate Edge Runtime

Cloudflare self-hosting compiles OpenSEO into a Workers script that executes in Cloudflare's distributed edge network. The runtime is a sandboxed V8 isolate, not a traditional Node.js process.


# Cloudflare quick-start

git clone https://github.com/every-app/open-seo.git
cd open-seo
corepack enable && pnpm install
pnpm alchemy login                      # grant Cloudflare scopes

pnpm alchemy cloudflare bootstrap       # provision state-store worker

cp .env.selfhost.example .env.selfhost
pnpm deploy:selfhost --yes              # deploys Worker + Access gate

Key runtime characteristics:

  • No server management—Cloudflare provisions and scales automatically
  • Execution happens at the nearest edge data center
  • Cold-start latency measured in milliseconds globally

Authentication and Access Control

Authentication models differ dramatically between the two hosting options.

Docker: External Protection Required

Docker self-hosting runs with AUTH_MODE=local_noauth, effectively disabling built-in authentication checks. The default admin account is admin@localhost with no verification.

Security implication: You must place the Docker service behind your own reverse proxy, VPN tunnel, or private network. Common patterns include:

  • Nginx reverse proxy with basic auth or OAuth
  • Tailscale or WireGuard tunnel for private access
  • Corporate network isolation

Cloudflare: Built-in Zero-Trust Access

Cloudflare self-hosting automatically provisions Cloudflare Access during deployment. The pnpm deploy:selfhost command creates an Access application gating the Worker.

Access control works through:

  • ACCESS_ALLOWED_EMAILS environment variable listing permitted users
  • Cloudflare's identity provider integrations (Google, GitHub, SAML, etc.)
  • Zero-trust enforcement at the edge—before requests reach your application

This model requires no additional infrastructure for secure public exposure.

Data Persistence Architecture

Where and how data survives defines operational boundaries for each option.

Docker: Local SQLite with Volume Mounts

By default, Docker self-hosting uses a file-based SQLite database living inside the container filesystem. The compose.yaml can persist this via Docker volumes:

  • Database path: Inside container (configurable via environment)
  • Backup responsibility: Entirely yours
  • Migration handling: Executed by docker-entrypoint.sh on startup

Volume configuration example from compose.yaml:

volumes:
  - ./data:/app/data  # Persist SQLite across container restarts

Cloudflare: Managed D1, KV, and R2

Cloudflare self-hosting provisions three managed services automatically:

Service Purpose Managed By
D1 Serverless SQLite-compatible database Cloudflare (auto-migrated on deploy)
KV Key-value namespaces for caching Cloudflare
R2 S3-compatible object storage for static assets Cloudflare

The pnpm deploy:selfhost workflow:

  1. Creates D1 database if missing
  2. Runs schema migrations
  3. Provisions KV namespaces
  4. Configures R2 bucket access
  5. Publishes Worker with bound resources

Deployment and Update Workflows

Operational simplicity versus flexibility presents a clear trade-off.

Docker: Compose Orchestration

Deployment uses standard Docker tooling:

docker compose pull && docker compose up -d

Key files controlling behavior:

  • compose.yaml — service definition, port mapping, environment variables
  • .env — runtime configuration including OPEN_SEO_IMAGE for version pinning
  • Dockerfile.selfhost — local image builds when registry pulls aren't desired

Updates require explicit action: pull new images or rebuild, then restart containers.

Cloudflare: CLI-Driven Serverless Deployment

The Cloudflare path uses custom pnpm scripts wrapping the Alchemy framework:

Command Purpose
pnpm alchemy login Authenticate with Cloudflare API
pnpm alchemy cloudflare bootstrap Provision state management resources
pnpm deploy:selfhost --yes Full deployment with automatic resource provisioning

Update workflow is streamlined:

git pull
pnpm deploy:selfhost

No container restarts, no volume management—just code propagation to Cloudflare's edge.

Scalability and Availability Characteristics

Docker: Single-Host Bound

Scaling Docker deployments requires manual infrastructure work:

  • Vertical scaling: Resize the host VM
  • Horizontal scaling: Run multiple containers behind a load balancer
  • High availability: Orchestrate with Kubernetes or Swarm, or accept single-point-of-failure

The simplicity comes with explicit limits.

Cloudflare: Automatic Edge Distribution

Cloudflare Workers inherit the platform's global infrastructure:

  • Automatic scaling: Zero configuration from 0 to millions of requests
  • Geographic distribution: Every request served from nearest data center
  • Availability SLA: Backed by Cloudflare's network uptime guarantees

No load balancers to configure, no health checks to maintain.

Prerequisites Comparison

Requirement Docker Cloudflare
Runtime Docker Desktop or Engine + Compose Node 22+, pnpm
Account needs None (self-contained) Cloudflare account with R2 enabled
API credentials DataForSEO API key DataForSEO API key
Network knowledge Reverse proxy setup for security DNS configuration (optional but typical)

Telemetry and Observability

Both hosting options share identical telemetry behavior. Anonymous usage events are enabled by default and can be disabled:


# Both environments

OPENSEO_TELEMETRY_DISABLED=1

No operational difference in observability tooling between deployment models.

Decision Framework: Which Self-Hosting Option for OpenSEO?

Choose Docker self-hosting when:

  • You need complete runtime control and inspection
  • Infrastructure must stay on-premises or in a specific cloud region
  • You already operate container orchestration platforms
  • Network isolation is preferred over identity-based access

Choose Cloudflare self-hosting when:

  • You want public accessibility without operational overhead
  • Global performance matters for distributed users
  • Zero-trust security with minimal configuration is desired
  • You prefer serverless economics over capacity planning

Summary

  • Docker self-hosting packages OpenSEO in a node:22 container with local SQLite, AUTH_MODE=local_noauth, and docker compose orchestration—ideal for controlled environments requiring manual security layering.

  • Cloudflare self-hosting deploys to edge Workers with managed D1/KV/R2 persistence, Cloudflare Access authentication, and one-command pnpm deploy:selfhost updates—optimized for production-scale, globally distributed deployments.

  • The Dockerfile.selfhost and compose.yaml define the container path, while pnpm alchemy tooling and scripts/selfhost-deploy-preflight.mjs automate the Cloudflare path.

  • Both options preserve the same telemetry controls and core application logic; the choice depends on operational constraints rather than feature differences.

Frequently Asked Questions

Can I switch from Docker to Cloudflare self-hosting without losing data?

Migration requires manual steps since storage backends differ fundamentally. Export your SQLite database from Docker using standard SQLite tools, then import into Cloudflare D1 using the D1 API or CLI. The schema is compatible, but you'll need to handle data migration outside the standard deployment flow.

Does Cloudflare self-hosting cost more than Docker?

Cloudflare's free tier covers modest usage, but production workloads incur charges for Workers invocations, D1 queries, KV operations, and R2 storage. Docker on your own infrastructure has fixed resource costs regardless of request volume. For high-traffic scenarios, Cloudflare's pay-per-use often beats provisioned server costs; for low-traffic internal tools, Docker may be cheaper.

Why does Docker disable authentication while Cloudflare enables it?

The Docker path assumes you'll implement network-level protection (VPN, private subnets, reverse proxy) appropriate to your environment. Disabling application auth simplifies container start-up and avoids false security for internet-exposed deployments. Cloudflare Access provides robust, managed authentication that integrates cleanly with the edge deployment model, making it the natural default for that path.

Can I run Cloudflare self-hosting on a custom domain?

Yes. After pnpm deploy:selfhost, configure your domain in Cloudflare's dashboard and attach it to the deployed Worker. Cloudflare Access will gate the custom domain just as it does the *.workers.dev subdomain. The documentation at docs/SELF_HOSTING_CLOUDFLARE.md covers DNS and certificate provisioning.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →