---
title: "Deploying to Cloudflare Pages with GitHub Actions"
description: "A simple CI/CD workflow for deploying static site to Cloudflare Pages using GitHub Actions"
date: 2026-03-26
updatedDate: 
tags: ['CI/CD', 'Gitea', 'Cloudflare Pages', 'DevOps']
authors:
  - "Patrick Jaroszewski"
slug: "gitea-cloudflare-deployment"
---

# Deploying to Cloudflare Pages with Gitea Actions

Cloudflare Pages has built-in integrations with GitHub and GitLab, but if you're self-hosting with Gitea or want more control over your deployment pipeline, you'll need to set things up manually. Fortunately, Gitea Actions is compatible with GitHub Actions, which means Cloudflare's official Wrangler action works out of the box.

This post walks through the workflow I use to deploy this site and others I host for clients.

---

## Prerequisites

Before getting started, you'll need:

- A Gitea instance with Actions enabled ([documentation](https://docs.gitea.com/usage/actions/overview))
- A registered Gitea runner (ubuntu-latest or equivalent)
- A Cloudflare account with access to Pages
- An existing Cloudflare Pages project
- Your project set up with pnpm (adjust commands for npm/yarn as needed)

---

## The Basic Workflow

Here's a minimal Gitea Actions workflow that builds an Astro site and deploys it to Cloudflare Pages. Note that Gitea uses `.gitea/workflows/` instead of GitHub's `.github/workflows/`.

```yaml title=".gitea/workflows/deploy.yaml"
name: Generate a build and push to Cloudflare Pages

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    name: Build and Deploy to Cloudflare Pages

    steps:
      - name: git-checkout
        uses: actions/checkout@v5

      - name: pnpm-setup
        uses: pnpm/action-setup@v4
        with:
          version: 10

      - name: Install Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm build

      - name: Deploy
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy ./dist --project-name="your-project-name"
```

> [!WARNING]
> If your Cloudflare Pages project doesn't exist yet, create it before running the workflow, otherwise it will fail.


The workflow triggers on pushes to `main`, installs dependencies, builds the site, and deploys using Cloudflare's Wrangler action. Every push to main goes straight to production.

> [!NOTE]
> Adjust `./dist` to match your framework's output directory—this is Astro's default. For example, Next.js static exports use `out`.

---

## Required Secrets

You'll need to configure two secrets in your Gitea repository (Settings → Actions → Secrets):

- `CLOUDFLARE_API_TOKEN` — Create this in the Cloudflare dashboard: Profile → API Tokens → Create Token → Use the "Edit Cloudflare Pages" template (or create a custom token with **Account: Cloudflare Pages: Edit** permission)
- `CLOUDFLARE_ACCOUNT_ID` — Found in the Cloudflare dashboard sidebar under **Workers & Pages → Overview**, or in the URL when viewing any Pages project (`dash.cloudflare.com/<account-id>/pages`)

---

## Using a Separate Production Branch

Deploying on every push to `main` works well for small projects, but you might want more control over when deployments happen. Some reasons to consider a separate deployment branch:

- **Batching changes** — deploy multiple commits together rather than one at a time
- **Review before release** — test locally or stage changes before they go live
- **Avoiding accidental deployments** — a typo fix shouldn't necessarily trigger a full deploy
- **Coordinating releases** — deploy on your schedule, not whenever you happen to push

The setup is simple: change the workflow trigger from `main` to `prod`:

```yaml
on:
  push:
    branches:
      - prod
```

Then add a script to promote `main` to `prod` when you're ready:

```json title="package.json"
{
  "scripts": {
    "push:prod": "git push origin main:prod"
  }
}
```

Now you develop freely on `main`, and run `pnpm push:prod` when you want to go live.

---

## The Cloudflare Branch Gotcha

If you use a `prod` branch, there's a catch: Cloudflare Pages uses the **branch name** to determine the deployment environment.

- `main` or `master` → production deployment (your primary URL)
- Any other branch → preview deployment (e.g., `branch-name.your-project.pages.dev`)

If you deploy from a branch called `prod`, Cloudflare treats it as a preview deployment, not production. Your site ends up at a subdomain instead of your main URL.

**Two solutions:**

1. **Change the production branch in Cloudflare** — Go to your Pages project → Settings → Builds & deployments → Production branch, and change it from `main` to `prod`.

2. **Use the `--branch` flag** — Force Wrangler to treat the deployment as production:

```yaml
command: pages deploy ./dist --project-name="your-project" --branch=main
```

Option 1 is cleaner because it aligns Cloudflare's understanding with your actual workflow. Option 2 is a quick workaround if you can't change the settings.

---

## Using a Preview Branch

Since Cloudflare treats all other branches as preview deployments, you can easily deploy a preview of your site by triggering the workflow on any branch (e.g., `preview`).

```yaml
on:
  push:
    branches:
      - prod
      - preview
```

Pushing to the `preview` branch deploys your site to `preview.your-project.pages.dev`. To make this convenient, add another script to your `package.json`:

```json title="package.json"
{
  "scripts": {
    "push:prod": "git push origin main:prod",
    "push:preview": "git push origin main:preview"
  }
}
```

---

## Wrapping Up

Whether you deploy on every push to `main` or use a separate `prod` branch, the core setup is the same: Gitea Actions builds your site and Wrangler pushes it to Cloudflare.
