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)
- 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/.
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:
on:
push:
branches:
- prodThen add a script to promote main to prod when you’re ready:
{
"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.
mainormaster→ 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:
-
Change the production branch in Cloudflare — Go to your Pages project → Settings → Builds & deployments → Production branch, and change it from
maintoprod. -
Use the
--branchflag — Force Wrangler to treat the deployment as production:
command: pages deploy ./dist --project-name="your-project" --branch=mainOption 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).
on:
push:
branches:
- prod
- previewPushing to the preview branch deploys your site to preview.your-project.pages.dev. To make this convenient, add another script to your 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.