Skip to content

Cloudflare Pages

Deploy static sites (React, Vue, Astro, etc.) to Cloudflare Pages as production or preview deployments. Pilum installs dependencies, runs your build and uploads the output with wrangler pages deploy.

Prerequisites

  1. Node.js and npm (or pnpm) installed
  2. Wrangler, run through npx (no global install needed)
  3. A Cloudflare account with a Pages project already created
  4. A Cloudflare API token with Pages edit permission

Create the Pages Project

Create the Cloudflare Pages project before the first deploy. The project name must exactly match the name field in your pilum.yaml.

Option A: Cloudflare Dashboard

  1. Go to Workers & Pages in the sidebar
  2. Click Create → Pages
  3. Choose Direct Upload (not “Connect to Git”; Pilum handles the deploy)
  4. Name the project to match your pilum.yaml name field
  5. Click Create. You don’t need to upload anything.

Option B: Wrangler CLI

Terminal window
npx wrangler pages project create my-site --production-branch main

API Token

Terminal window
# Create an API token:
# Cloudflare Dashboard → My Profile → API Tokens → Create Token
# Use the "Edit Cloudflare Workers" template, or a custom token with
# Account → Cloudflare Pages → Edit
export CLOUDFLARE_API_TOKEN="your-token-here"

Service Configuration

name: my-site
type: cloudflare-pages
cloudflare:
account_id: "abc123def456"
token_env: "CLOUDFLARE_API_TOKEN"
production_branch: "main"
package_manager: "npm"
build:
cmd: "npm run build"
output_dir: "dist"

Use type: cloudflare-pages (or provider: cloudflare).

Required Fields

FieldDescription
nameProject name; must match your Cloudflare Pages project name
cloudflare.account_idCloudflare account ID

Optional Fields

Cloudflare Configuration

FieldTypeDefaultDescription
cloudflare.token_envstringCLOUDFLARE_API_TOKENName of the environment variable holding the API token
cloudflare.account_id_envstring-Name of an environment variable holding the account ID. When set, it is used instead of account_id.
cloudflare.production_branchstringmainTag value that deploys to production
cloudflare.package_managerstringnpmnpm or pnpm. Any other value uses npm.

account_id_env keeps the account ID out of a public repository. It is not in the JSON schema yet, and pilum check still asks for account_id, so set account_id to a placeholder if you want check to pass.

Build Configuration

FieldTypeDefaultDescription
build.cmdstring-Build command. If empty, the build step is skipped.
build.output_dirstringdistDirectory to upload, relative to the service directory

There is no default build command at deploy time. If you leave out build.cmd, nothing is built and wrangler uploads whatever is already in output_dir.

Where Each Step Runs

StepRuns in
install dependenciesThe directory you run pilum in (normally the repository root)
build siteThe directory you run pilum in
deploy to pagesThe service directory (where pilum.yaml is)

So npm ci/pnpm install and build.cmd run from the root, while output_dir is resolved from the service directory. In a single-site repo with pilum.yaml at the root these are the same place.

Deployment Steps

The Cloudflare Pages recipe runs:

  1. install dependencies [build] — npm ci, or pnpm install --frozen-lockfile
  2. build site [build] — build.cmd through /bin/sh -c. Skipped when build.cmd is empty.
  3. deploy to pages [deploy] — npx wrangler pages deploy <output_dir> --project-name=<name> --branch=<branch>

Production vs Preview Deploys

The --tag value picks the deployment type:

  • Production: the tag equals production_branch (default main), or is latest. Pilum passes --branch=<production_branch>.
  • Preview: any other tag. Pilum passes --branch=<tag>.
Terminal window
# Production deploy
pilum deploy my-site --tag=main
# Preview deploy (e.g., from a feature branch)
pilum deploy my-site --tag=feature-login

Preview deployments get a URL like https://<branch>.my-site.pages.dev.

Package Manager

npm (default)

cloudflare:
package_manager: "npm"

Installs with npm ci.

pnpm

cloudflare:
package_manager: "pnpm"

Installs with pnpm install --frozen-lockfile.

Yarn and bun are not supported for Cloudflare Pages; the schema’s yarn option falls back to npm.

pnpm Monorepo

For a site that is one package in a pnpm workspace, put pilum.yaml in the site’s package directory, run Pilum from the workspace root, and filter the build:

apps/my-site/pilum.yaml
name: my-site
type: cloudflare-pages
cloudflare:
account_id: "abc123def456"
package_manager: "pnpm"
build:
cmd: "pnpm --filter @scope/my-site... build"
output_dir: "dist" # apps/my-site/dist, relative to this directory

The ... suffix builds the package and all its workspace dependencies in order. Install and build run from the workspace root; the deploy runs in apps/my-site, so output_dir is the package’s own dist.

Build Step

build:
cmd: "npm run build"
output_dir: "dist"

Common output directories by framework:

FrameworkOutput Dir
Vite / React / Vuedist
Next.js (static)out
Astrodist
Create React Appbuild

Generated Deploy Script

The deploy step runs:

Terminal window
if [ -z "$CLOUDFLARE_API_TOKEN" ]; then
echo "Error: CLOUDFLARE_API_TOKEN environment variable is not set"
exit 1
fi
export CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN"
export CLOUDFLARE_ACCOUNT_ID='abc123def456'
npx wrangler pages deploy 'dist' --project-name='my-site' --branch='main'

With account_id_env: CF_ACCOUNT_ID, the account line becomes a check that $CF_ACCOUNT_ID is set, followed by export CLOUDFLARE_ACCOUNT_ID="$CF_ACCOUNT_ID".

Example Commands

Terminal window
# Full pipeline (install, build, deploy) to production
pilum deploy my-site --tag=main
# Install and build only
pilum build my-site
# Preview commands
pilum dry-run my-site --tag=main
# Deploy only (output_dir must already be built)
pilum deploy my-site --only-tags=deploy --tag=main
# Preview deployment from a feature branch
pilum deploy my-site --tag=feature-auth

Status and Logs

pilum logs my-site (and pilum status my-site) run npx wrangler pages deployment list --project-name my-site. pilum logs prints that deployment list. pilum status currently shows the state as Unknown for Pages projects, or Error if wrangler fails, so use pilum logs or the dashboard to see deployments.

There is no pilum rollback for Cloudflare Pages; use the dashboard’s rollback.

CI/CD Integration

GitHub Actions (pilum-action)

name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Deploy with Pilum
uses: SID-Technologies/pilum-action@v1
with:
command: deploy
services: my-site
tag: ${{ github.ref_name }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

GitHub Actions (manual install)

Release archives are named pilum_<version>_<os>_<arch>.tar.gz, so download by pattern:

name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Pilum
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release download --repo SID-Technologies/Pilum --pattern '*_linux_amd64.tar.gz' --output pilum.tar.gz
tar xzf pilum.tar.gz pilum
sudo mv pilum /usr/local/bin/
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: pilum deploy my-site --tag=${{ github.ref_name }}

github.ref_name is the branch name, so pushes to main deploy to production and the same workflow on other branches creates preview deployments.

Troubleshooting

Authentication failed

Error: CLOUDFLARE_API_TOKEN environment variable is not set

Export the variable named in cloudflare.token_env:

Terminal window
export CLOUDFLARE_API_TOKEN="your-token-here"

Project not found

Error: A request to the Cloudflare API failed. Could not find project

The name in your pilum.yaml must exactly match your Cloudflare Pages project name. Create the project first in the dashboard or with:

Terminal window
npx wrangler pages project create my-site --production-branch main

Deploy went to preview instead of production

The tag did not match production_branch. Check the --branch= value in pilum dry-run; an empty --branch='' means --tag was not passed.

Wrong output directory

No files found in dist/

build.output_dir is relative to the service directory, not the directory you run Pilum from. Check where your framework writes its output:

build:
cmd: "npm run build"
output_dir: "build" # e.g., for Create React App

Wrangler not found

npx: command not found: wrangler

Ensure Node.js and npm are installed. Wrangler is run through npx, so it doesn’t need a global install; adding it as a dev dependency pins the version.

Next Steps