# How to Set Up a CI/CD Pipeline for OpenSEO: A Complete GitHub Actions and Cloudflare Guide

> Set up a CI/CD pipeline for OpenSEO with GitHub Actions and Cloudflare Workers. Automate linting testing building and deployments for efficient OpenSEO management.

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

---

**You can set up a CI/CD pipeline for OpenSEO by configuring GitHub Actions workflows for linting, testing, and building, then automating deployments to Cloudflare Workers using Alchemy, with secure secret management and isolated PR preview stages.**

OpenSEO (every-app/open-seo) uses a modern CI/CD architecture built around **pnpm**, **GitHub Actions**, and **Cloudflare Workers**. The pipeline ensures code quality through automated checks while enabling safe, previewable deployments for every pull request. This guide walks through the exact configuration files and commands used in the repository to replicate this setup.

## Continuous Integration Pipeline

The foundation of the OpenSEO CI/CD pipeline is the **GitHub Actions workflow** defined in [`.github/workflows/ci.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/ci.yml). This workflow runs on every push and pull request to validate code integrity before it reaches production.

### The CI Workflow Structure

The `ci` job executes on `ubuntu-latest` and follows a deterministic sequence of steps:

1. **Environment setup** – Checks out the exact commit using `actions/checkout@v4`, then installs pnpm via `pnpm/action-setup@v4` and Node.js 22 using `actions/setup-node@v4`
2. **Dependency installation** – Runs `pnpm install --frozen-lockfile` to ensure reproducible builds using the locked dependency tree
3. **Static analysis** – Executes `pnpm run ci:check` which runs linting (oxlint) and dead code detection (knip)
4. **Test execution** – Runs `pnpm run test:ci` to execute the full test suite
5. **Bundle verification** – Builds the worker with `pnpm vite build`, which includes a guard that fails if deny-listed packages (such as `dataforseo-client`) enter the eager startup graph
6. **Web UI validation** – Installs web dependencies with `pnpm --dir web install`, runs TypeScript checks via `pnpm --dir web run types:check`, and builds the static site using `pnpm --dir web run build`

All steps are configured to **fail fast**, ensuring that lint errors, type mismatches, or test failures block the pipeline immediately.

### Local CI Verification

Developers can replicate the CI environment locally before pushing changes:

```bash

# Enable corepack for the exact pnpm version specified in package.json

corepack enable

# Install dependencies with frozen lockfile

pnpm install --frozen-lockfile

# Run the full CI check suite

pnpm run ci:check

# Execute tests

pnpm run test:ci

# Verify worker bundle (includes dependency guard)

pnpm vite build

```

## Continuous Delivery and Deployment

OpenSEO implements a multi-stage delivery process that includes Docker image builds for self-hosting and Cloudflare Worker deployments for the hosted version.

### Docker Image Build

The [`.github/workflows/docker-image.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/docker-image.yml) workflow produces a self-hostable container using `Dockerfile.selfhost`:

- Uses `docker/build-push-action@v6` with GitHub Actions cache (`type=gha`) to speed up subsequent builds
- Targets `linux/amd64` architecture for broad compatibility
- Loads the image locally for verification rather than pushing to a registry automatically

To build the image manually:

```bash
docker build -f Dockerfile.selfhost -t openseo:selfhost .

```

### PR Preview Deployments

The **PR preview workflow** in [`.github/workflows/pr-preview.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/pr-preview.yml) creates isolated Cloudflare Worker stages for every pull request, enabling safe testing of changes without affecting production.

The workflow executes these critical steps:

1. **Environment preparation** – Writes the secret `ENV_PREVIEW` to `.env.preview` using `printf '%s' "$ENV_PREVIEW" > .env.preview`
2. **URL derivation** – Extracts `WORKERS_SUBDOMAIN` from the environment file to construct the preview URL: `https://open-seo-<stage>.<subdomain>`
3. **Deployment** – Runs `pnpm deploy:preview --stage "$STAGE" --yes`, which triggers a Vite build and Alchemy deployment
4. **Security verification** – Uses `curl` to verify the preview URL redirects to Cloudflare Access authentication (`https://*.cloudflareaccess.com/...`), failing the job if the stage is publicly accessible
5. **PR integration** – Posts the preview URL as a comment using the `gh` CLI
6. **Cleanup** – When the PR closes, automatically runs `pnpm destroy:preview --stage "$STAGE" --yes` to remove the temporary infrastructure

Required repository secrets for this workflow:
- `CLOUDFLARE_API_TOKEN`
- `CLOUDFLARE_ACCOUNT_ID`
- `ENV_PREVIEW` (contains the full `.env.preview` file contents)

### Production Deployment

Production deployments use the same Alchemy tooling but target a fixed stage name `hosted-prod`. Unlike the automated PR previews, production pushes are typically performed manually to prevent accidental secret exposure:

```bash

# Run database migrations and deploy to production

pnpm deploy:postgres

```

This command reads from `.env.production` and executes the full deployment stack.

## Required Secrets and Configuration

To set up the complete CI/CD pipeline, configure these **GitHub Actions secrets** in your repository settings:

| Secret | Purpose |
|--------|---------|
| `CLOUDFLARE_API_TOKEN` | Authenticates with Cloudflare API for Worker deployments |
| `CLOUDFLARE_ACCOUNT_ID` | Identifies the target Cloudflare account |
| `ENV_PREVIEW` | Base64-encoded or raw contents of `.env.preview` containing `WORKERS_SUBDOMAIN` and other preview-stage variables |

The pipeline strictly validates secret presence early in each workflow run. If `ENV_PREVIEW` is missing or malformed, the PR preview job aborts immediately to prevent deployment failures.

For local preview deployments (mirroring CI behavior), authenticate once with Alchemy:

```bash
pnpm alchemy login

```

Then create your local environment file:

```bash
cp .env.preview.example .env.preview

# Edit .env.preview with your values

pnpm preview:access  # Sets up the shared Cloudflare Access gate (idempotent)

pnpm deploy:preview --stage my-feature --yes

```

## Summary

- **OpenSEO's CI pipeline** uses [`.github/workflows/ci.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/ci.yml) to run linting, type-checking, tests, and bundle verification on every push
- **The delivery process** combines Docker image builds ([`docker-image.yml`](https://github.com/every-app/open-seo/blob/main/docker-image.yml)) with Cloudflare Worker deployments via Alchemy
- **PR previews** create isolated, Access-protected stages for each pull request using [`.github/workflows/pr-preview.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/pr-preview.yml)
- **Security is enforced** through deny-list bundle guards, mandatory Access authentication on previews, and strict secret management via GitHub Actions
- **Local development** can replicate all CI steps using pnpm scripts defined in [`package.json`](https://github.com/every-app/open-seo/blob/main/package.json)

## Frequently Asked Questions

### What Node.js version does OpenSEO require?

OpenSEO requires **Node.js 22**, as specified in the GitHub Actions workflows using `actions/setup-node@v4`. The CI pipeline uses pnpm 9.x (managed via `corepack`) for deterministic dependency resolution across all environments.

### How does OpenSEO prevent heavy dependencies from bloating the worker bundle?

The build process includes a **bundle guard** in the Vite build step (`pnpm vite build`) that explicitly checks for deny-listed packages like `dataforseo-client`. If any forbidden package enters the worker's eager startup graph, the build fails immediately, preventing accidental inclusion of heavy dependencies that would degrade cold-start performance.

### Can I deploy OpenSEO without using Cloudflare?

Yes. While the hosted version uses Cloudflare Workers, the repository includes a `Dockerfile.selfhost` and a [`docker-image.yml`](https://github.com/every-app/open-seo/blob/main/docker-image.yml) workflow for building self-hostable containers. You can run the Docker image in any container runtime (Kubernetes, AWS ECS, etc.) by providing the necessary environment variables manually rather than using Alchemy.

### Why are production deployments manual rather than automated?

Production deployments use `pnpm deploy:postgres` with a fixed `hosted-prod` stage but are intentionally excluded from automatic GitHub Actions triggers to prevent accidental exposure of production secrets or erroneous pushes. This manual gate ensures that production credentials remain isolated from CI environments and that deploys require explicit human verification.