# CI/CD Pipeline Configuration in open-seo: Complete GitHub Actions Guide

> Learn how to configure the CI/CD pipeline in open-seo with GitHub Actions. Automate testing, building, and deploying with four essential workflows.

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

---

**The open-seo repository uses four GitHub Actions workflows—CI, Docker image build, sourcemap upload, and PR preview—to automate testing, building, and deploying on every push and pull request.**

This guide walks through the complete CI/CD pipeline configuration in **open-seo**, an open-source SEO tool built by every-app. Every workflow lives in `.github/workflows` and runs on GitHub Actions, providing a fully automated path from code commit to production deployment. Whether you are extending the project or adapting these patterns for your own repository, understanding the workflow architecture is essential.

## Core CI Workflow: Testing and Validation

The **[CI workflow](https://github.com/every-app/open-seo/blob/main/.github/workflows/ci.yml)** ([`ci.yml`](https://github.com/every-app/open-seo/blob/main/ci.yml)) serves as the entry point for all code changes. It triggers on every push to `main` and every pull request, ensuring that no broken code reaches the default branch.

The workflow performs the following sequence:

1. **Install dependencies** using pnpm with frozen lockfile enforcement
2. **Run lint and type-check** via `ci:check`
3. **Execute unit tests** with `test:ci`
4. **Build the Vite-based worker bundle** for the core application
5. **Compile the website** from the `web/` directory

Each step mirrors commands you can run locally:

```bash
pnpm install --frozen-lockfile
pnpm run ci:check
pnpm run test:ci
pnpm vite build
pnpm --dir web install && pnpm --dir web run build

```

The workflow uses `actions/setup-node@v4` with `cache: pnpm` to speed up dependency installation across runs.

## Docker Image Build: Multi-Architecture Publishing

The **[Docker workflow](https://github.com/every-app/open-seo/blob/main/.github/workflows/docker-image.yml)** ([`docker-image.yml`](https://github.com/every-app/open-seo/blob/main/docker-image.yml)) produces reproducible container images for self-hosting. It triggers on pushes to `main` or any `v*` tag, plus manual dispatch via `workflow_dispatch`.

Key technical characteristics:

- **Multi-architecture support** using QEMU and Buildx for `linux/amd64` and `linux/arm64`
- **GitHub Container Registry (GHCR)** as the push target
- **Docker build cache** via `type=gha` for faster rebuilds
- **Automatic metadata** generation with `docker/metadata-action@v5`

The build uses `Dockerfile.selfhost` rather than the default Dockerfile, producing images tagged with both semantic versions and `latest`.

Trigger manually when needed:

```bash
gh workflow run docker-image.yml --ref main

```

## Sourcemap Upload: Production Error Tracking

The **[sourcemaps workflow](https://github.com/every-app/open-seo/blob/main/.github/workflows/sourcemaps.yml)** ([`sourcemaps.yml`](https://github.com/every-app/open-seo/blob/main/sourcemaps.yml)) runs exclusively on `main` branch pushes. It compiles JavaScript sourcemaps and uploads them to **PostHog**, enabling de-obfuscation of production stack traces.

This workflow requires the `POSTHOG_CLI_TOKEN` secret and executes after the main build completes. Sourcemaps bridge the gap between minified production code and readable source, making runtime error investigation practical.

## PR Preview: On-Demand Staging Environments

The **[PR preview workflow](https://github.com/every-app/open-seo/blob/main/.github/workflows/pr-preview.yml)** ([`pr-preview.yml`](https://github.com/every-app/open-seo/blob/main/pr-preview.yml)) deploys temporary staging environments on Cloudflare Workers for every pull request. It responds to `opened`, `synchronize`, `reopened`, and `closed` events.

Deployment lifecycle:

1. **Extract configuration** from `ENV_PREVIEW` secret (contains `.env.preview` content including `WORKERS_SUBDOMAIN`)
2. **Deploy to Cloudflare Workers** using Wrangler with a stage name derived from the PR number
3. **Verify Cloudflare Access protection** is active on the preview URL
4. **Post PR comment** with the preview URL for reviewer access
5. **Teardown on close** when the PR is merged or abandoned

Access the preview from the comment:

```bash

# Posted comment format:

# **Preview deployed** (stage `pr-12`): https://open-seo-pr-12.example.workers.dev

curl -I https://open-seo-pr-12.example.workers.dev

```

## Shared Workflow Architecture

All four workflows in open-seo share common architectural patterns that ensure reliability and performance.

### Concurrency Control

Every workflow declares a `concurrency` block to prevent redundant runs:

```yaml
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

```

This ensures only the latest run for any given branch or PR executes, automatically cancelling stale jobs.

### Secrets and Environment Variables

Sensitive configuration flows through GitHub's secrets system:

- `POSTHOG_CLI_TOKEN`: Sourcemap upload authentication
- `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`: Workers deployment credentials
- `ENV_PREVIEW`: Complete `.env.preview` file for staging configuration

Non-sensitive values use `${{ vars... }}` where appropriate.

### Caching Strategy

Two caching layers accelerate the pipeline:

| Layer | Implementation | Benefit |
|-------|---------------|---------|
| Node dependencies | `actions/setup-node@v4` with `cache: pnpm` | ~30-60s saved per run |
| Docker layers | `type=gha` Buildx cache | Faster multi-arch builds |

## Workflow File Reference

| File | Purpose | Trigger Events |
|------|---------|--------------|
| [`.github/workflows/ci.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/ci.yml) | Install, lint, test, build | `push` to `main`, all PRs |
| [`.github/workflows/docker-image.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/docker-image.yml) | Multi-arch image build and push | `push` to `main` or `v*` tags, `workflow_dispatch` |
| [`.github/workflows/sourcemaps.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/sourcemaps.yml) | Build and upload PostHog sourcemaps | `push` to `main` |
| [`.github/workflows/pr-preview.yml`](https://github.com/every-app/open-seo/blob/main/.github/workflows/pr-preview.yml) | Deploy and manage PR staging environments | PR `opened`, `synchronize`, `reopened`, `closed` |

## Summary

- **Four workflows** handle the complete CI/CD lifecycle in open-seo: CI validation, Docker publishing, sourcemap upload, and PR preview deployment
- **Concurrency controls** prevent redundant runs and race conditions across all workflows
- **Multi-architecture Docker builds** ensure images run on both AMD64 and ARM64 infrastructure
- **Automated PR previews** on Cloudflare Workers give reviewers live staging URLs without manual intervention
- **Secrets-based configuration** keeps credentials out of source code while enabling Cloudflare and PostHog integrations

## Frequently Asked Questions

### How do I run the CI checks locally before pushing?

Execute the same commands defined in [`ci.yml`](https://github.com/every-app/open-seo/blob/main/ci.yml): `pnpm install --frozen-lockfile`, `pnpm run ci:check`, `pnpm run test:ci`, and the build commands for both the worker and website. This validates your changes without waiting for GitHub Actions.

### What triggers a new Docker image build?

The Docker workflow runs automatically on any push to `main` or a version tag matching `v*`. You can also trigger it manually with `gh workflow run docker-image.yml --ref main` for testing or emergency rebuilds.

### Where are the PR preview URLs posted?

The [`pr-preview.yml`](https://github.com/every-app/open-seo/blob/main/pr-preview.yml) workflow automatically posts a comment on each pull request containing the deployed preview URL. The URL follows the pattern `https://open-seo-pr-{NUMBER}.{SUBDOMAIN}.workers.dev` derived from your `WORKERS_SUBDOMAIN` configuration.

### Why does sourcemap upload only run on main branch pushes?

Sourcemaps are only needed for production error tracking, and production deployments originate from `main`. Running this on every PR would create unnecessary uploads and consume PostHog quota without providing value, since preview deployments use separate build configurations.