# How to Self-Host OpenSEO on Cloudflare Workers: A Complete Deployment Guide

> Self-host OpenSEO on Cloudflare Workers easily. Clone the repository, configure env, and deploy with Alchemy CLI for automated setup including D1, KV, R2, and Access.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-08-19

---

**Deploy OpenSEO to Cloudflare Workers by cloning the every-app/open-seo repository, configuring `.env.selfhost`, and running `pnpm deploy:selfhost --yes` via the Alchemy CLI, which automatically provisions D1, KV, R2, and Cloudflare Access protections.**

OpenSEO is an open-source SEO dashboard designed to run entirely on Cloudflare's serverless edge. When you self-host OpenSEO on Cloudflare Workers, you retain full control over your data while leveraging native services like **D1** for relational storage, **KV** for caching, and **R2** for object storage. This guide walks through the exact deployment steps, file paths, and commands defined in the `every-app/open-seo` source code.

## Architecture of Self-Hosted OpenSEO on Cloudflare Workers

The OpenSEO architecture is built around a single Worker bundle that binds to multiple Cloudflare data stores and is gated by Cloudflare Access. All resources are namespaced with the stage name `selfhost`, allowing isolated deployments within the same Cloudflare account.

### Worker Runtime and TypeScript Bindings

The core application logic is implemented in TypeScript and compiled into a single Worker bundle. The environment bindings and variables are typed in [[`worker-configuration.d.ts`](https://github.com/every-app/open-seo/blob/main/worker-configuration.d.ts)](https://github.com/every-app/open-seo/blob/main/worker-configuration.d.ts), ensuring type-safe access to D1, KV, and R2 inside the Worker runtime.

### Cloudflare Data Stores

OpenSEO relies on three managed data services provisioned during deployment:

- **D1 database** – Persistent relational storage for projects, users, and audit data. Migrations live in [`drizzle-pg/*.sql`](https://github.com/every-app/open-seo/tree/main/drizzle-pg) and are applied automatically.
- **KV namespaces** – Low-latency key/value storage for cached data and telemetry flags. Definitions are stored in [`wrangler.jsonc`](https://github.com/every-app/open-seo/blob/main/wrangler.jsonc).
- **R2 bucket** – Object storage for large assets such as screenshots and crawled pages. This is also declared in [`wrangler.jsonc`](https://github.com/every-app/open-seo/blob/main/wrangler.jsonc) and attached as a Worker binding.

### Zero-Trust Security with Cloudflare Access

Access to the deployed Worker is protected by a Cloudflare Access application created during deployment. Allowed emails are read from the `ACCESS_ALLOWED_EMAILS` variable in `.env.selfhost`, enforcing Zero-Trust gating at the edge before any request reaches the Worker.

## Prerequisites for Self-Hosting OpenSEO

Before you begin, ensure you have the following:

- **Node.js 22.6+** and **PNPM** enabled via Corepack.
- A **Cloudflare account** with R2 enabled (requires a payment method on file).
- An active **DataForSEO account** and API key (required for the SEO data source).

## Step-by-Step Deployment Guide

### Clone the Repository and Install Dependencies

Start by cloning the open-source repository and installing dependencies:

```bash
git clone https://github.com/every-app/open-seo.git
cd open-seo
corepack enable
pnpm install

```

### Authenticate with Cloudflare via Alchemy

OpenSEO uses the **Alchemy** CLI to manage infrastructure. Log in and bootstrap the Cloudflare state store:

```bash
pnpm alchemy login
pnpm alchemy cloudflare bootstrap

```

If you previously logged in without the `access:write` scope, re-authenticate with:

```bash
pnpm alchemy login --configure

```

### Configure Environment Variables

Copy the example environment file and edit it:

```bash
cp .env.selfhost.example .env.selfhost

```

Fill in the required values inside `.env.selfhost`:

- `DATAFORSEO_API_KEY` – Your DataForSEO API key.
- `ACCESS_ALLOWED_EMAILS` – Comma-separated list of emails allowed through Cloudflare Access.

You can view the full template in [`.env.selfhost.example`](https://github.com/every-app/open-seo/blob/main/.env.example). Optional variables include `TEAM_DOMAIN` and `POLICY_AUD` if you prefer to manage the Access application manually.

### Run the Deploy Command

Deploy the entire stack with one command defined in [[`alchemy.run.ts`](https://github.com/every-app/open-seo/blob/main/alchemy.run.ts)](https://github.com/every-app/open-seo/blob/main/alchemy.run.ts):

```bash
pnpm deploy:selfhost --yes

```

This command performs the following actions:

1. Provisions a D1 database, KV namespaces, and an R2 bucket (all suffixed with `selfhost`).
2. Runs the SQLite migrations located in `drizzle-pg/`.
3. Deploys the Worker bundle to Cloudflare.
4. Creates a Cloudflare Access application restricted to the emails listed in `.env.selfhost`.

After completion, the CLI prints your Worker URL (for example, `https://my-openseo.workers.dev`).

### Verify the Deployment

Open the printed URL in your browser. You will be prompted to authenticate through Cloudflare Access. Then confirm runtime health by calling the health endpoint:

```bash
curl https://<your-worker-hostname>/api/health

```

This endpoint reports runtime checks and database connectivity status. If you encounter issues, stream live Worker logs:

```bash
pnpm exec wrangler tail --env selfhost

```

## Post-Deployment Operations

### Updating Your Instance

To pull in upstream changes and redeploy:

```bash
git pull
pnpm install
pnpm deploy:selfhost --yes

```

If you forked the repository, replace `git pull` with the appropriate fetch and merge workflow.

### Managing Teammate Access

Add new emails to `ACCESS_ALLOWED_EMAILS` in `.env.selfhost` and redeploy. The Alchemy script updates the Access policy automatically. If you manage the Access application manually through the Zero-Trust dashboard, subsequent deploys will **not** overwrite your custom policy.

### Disabling Telemetry

OpenSEO sends anonymized usage telemetry by default. To disable it, add the following to `.env.selfhost` and redeploy:

```bash
OPENSEO_TELEMETRY_DISABLED=1

```

For details on MCP server telemetry and operations, refer to [[`docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md`](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md)](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md).

## Teardown and Cleanup

To completely remove the self-hosted instance and all associated data, run:

```bash
pnpm alchemy destroy --env-file .env.selfhost --stage selfhost

```

This destroys the Worker, D1 database, KV namespaces, R2 bucket, and the Cloudflare Access application. All stored data is permanently deleted.

## Summary

- OpenSEO runs as a single Cloudflare Worker with bindings to **D1**, **KV**, and **R2**, all configured in [`wrangler.jsonc`](https://github.com/every-app/open-seo/blob/main/wrangler.jsonc).
- The **Alchemy** CLI automates provisioning, migration, and deployment via `pnpm deploy:selfhost --yes`.
- Access is enforced through **Cloudflare Access**, with allowed emails defined in `.env.selfhost`.
- Health is verified at `/api/health`, and logs are tailed with `pnpm exec wrangler tail --env selfhost`.
- Destroy the entire stack cleanly using `pnpm alchemy destroy --env-file .env.selfhost --stage selfhost`.

## Frequently Asked Questions

### What Cloudflare services does OpenSEO require?

OpenSEO requires a **D1** database for relational data, **KV** for key-value caching, **R2** for object storage, and **Cloudflare Access** for Zero-Trust authentication. These are all provisioned automatically during deployment according to the [[`alchemy.run.ts`](https://github.com/every-app/open-seo/blob/main/alchemy.run.ts)](https://github.com/every-app/open-seo/blob/main/alchemy.run.ts) script.

### How do I add new users after deployment?

Add the user's email to the `ACCESS_ALLOWED_EMAILS` variable in `.env.selfhost` and run `pnpm deploy:selfhost --yes`. If you manually configured the Access application in the Zero-Trust dashboard, you can edit the Allow policy there instead; the deploy script will not overwrite manual changes.

### Can I disable telemetry in self-hosted OpenSEO?

Yes. Set `OPENSEO_TELEMETRY_DISABLED=1` inside `.env.selfhost` and redeploy. The telemetry endpoint is documented further in [[`docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md`](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md)](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md).

### How do I completely remove a self-hosted OpenSEO instance?

Run `pnpm alchemy destroy --env-file .env.selfhost --stage selfhost`. This command removes the Worker, D1, KV, R2 resources, and the Access application, permanently deleting all stored data associated with the `selfhost` stage.