packages.wenpai.net/docs/r2-deployment.md
Ben Word 7cb8fef01b
WP Packages rename (#42)
* Update all import paths

* Rename directory cmd/wpcomposer/ → cmd/wppackages/

* Rename import alias wpcomposergo → wppackagesgo in main.go and migrate_test.go

* Makefile — binary name wpcomposer → wppackages

* Update Air path

* Global replace repo.wp-composer.com → repo.wp-packages.org

* Global replace cdn.wp-composer.com → cdn.wp-packages.org

* Global replace wp-composer.com → wp-packages.org (remaining)

* Composer repo key in templates/docs: repositories.wp-composer → repositories.wp-packages

* Rename columns on the existing schema

* Update all Go code referencing these column names

* Routes & SEO

* Templates & front-end

* Admin UI

* Documentation

* CI/CD

* Config defaults

* Rename role directory

* Rename all systemd template files inside the role

* Update contents of all .j2 templates — service names, binary paths, descriptions

* Update tasks/main.yml and handlers/main.yml in the role

* Update deploy/ansible/roles/app/tasks/main.yml and deploy.yml

* Update deploy/ansible/group_vars/production/main.yml

* Additional renames/fixes

* Additional renames/fixes

* Additional renames/fixes

* not needed
2026-03-19 11:50:12 -05:00

5.2 KiB

R2 Deployment

WP Packages deploys built repository artifacts to Cloudflare R2 for serving via CDN. Builds are generated locally (fast filesystem I/O), then synced to R2 on deploy.

Deploy Model

Only p2/ metadata files and packages.json are stored on R2. Mutable p2/ files are overwritten in place when packages change. packages.json is uploaded last as the final step.

packages.json                                        ← root index (mutable)
p2/wp-plugin/akismet.json                            ← overwritten when changed
p2/wp-theme/astra.json                               ← overwritten when changed

The deploy diffs the current build against the previous build locally. Mutable p2/ files are byte-compared and only uploaded if changed. This reduces R2 operations to only the number of changed packages per build.

Prerequisites

  1. A Cloudflare account with R2 enabled.
  2. An R2 bucket created for the repository (e.g., wp-packages-repo).
  3. An R2 API token with read/write access to the bucket.
  4. AWS CLI v2 installed (for manual operations and debugging).

R2 Bucket Setup

Create the bucket

In the Cloudflare dashboard: R2 > Create bucket. Pick a name (e.g., wp-packages-repo), choose a location hint close to your server.

Create API credentials

R2 > Manage R2 API Tokens > Create API Token:

  • Permission: Object Read & Write
  • Scope: the specific bucket

Save the Access Key ID and Secret Access Key.

R2 > your bucket > Settings > Custom Domains > Connect Domain. This gives you a URL like https://repo.wp-packages.org backed by Cloudflare's CDN.

Without a custom domain, R2 provides a .r2.dev URL, but it has rate limits and no caching.

Environment Configuration

# R2 credentials
R2_ACCESS_KEY_ID=your-access-key-id
R2_SECRET_ACCESS_KEY=your-secret-access-key
R2_BUCKET=wp-packages-repo
R2_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com

# Enable R2 deploy
WP_PACKAGES_DEPLOY_R2=true

Find your account ID in the Cloudflare dashboard under R2 > Overview.

How Deploy Works

When deploying to R2 (wppackages deploy --to-r2):

  1. Validates the build (packages.json and manifest.json must exist).
  2. Uploads p2/ files in parallel — skips unchanged files (byte-compared against previous build). Each upload retries up to 3 times with exponential backoff.
  3. Uploads packages.json last.
  4. Promotes the local build symlink (for rollback capability).

If R2 sync fails, the local symlink is not updated — the previous build remains promoted.

Files are uploaded with Content-Type: application/json.

CDN Cache Headers

When using a Cloudflare custom domain on the R2 bucket, cache behavior is controlled by the Cache-Control headers set during upload:

Path pattern Cache-Control Rationale
packages.json max-age=300 Root index, mutable
p2/*.json max-age=300 Mutable, overwritten on package changes

URL Requirements

The generated packages.json contains:

  • metadata-url: /p2/%package%.json
  • notify-batch: absolute URL pointing to the app domain (not R2, not rewritten)
  • available-package-patterns: ["wp-plugin/*", "wp-theme/*"]

AWS CLI Setup (Manual Operations)

Configure a named profile for R2:

aws configure --profile r2

Enter:

  • Access Key ID: your R2 access key
  • Secret Access Key: your R2 secret key
  • Default region: auto
  • Default output format: json

Verify access

aws s3 ls s3://wp-packages-repo/ --profile r2 --endpoint-url https://<account-id>.r2.cloudflarestorage.com

List bucket contents

aws s3 ls s3://wp-packages-repo/p2/ --profile r2 --endpoint-url https://<account-id>.r2.cloudflarestorage.com

Cleanup legacy R2 objects

After dropping Composer v1 support, the following R2 prefixes are orphaned and can be manually deleted:

  • p/ — content-addressed v1 package files and provider group files
  • releases/ — per-release snapshots from the old deploy model

Use the AWS CLI to delete these when ready:

aws s3 rm s3://wp-packages-repo/p/ --recursive --profile r2 --endpoint-url https://<account-id>.r2.cloudflarestorage.com
aws s3 rm s3://wp-packages-repo/releases/ --recursive --profile r2 --endpoint-url https://<account-id>.r2.cloudflarestorage.com

Local build cleanup is handled by wppackages deploy --cleanup.

Rollback

Rollback deploys the target build to R2 — it diffs the target build's p2/ files against the currently deployed build and uploads only changed files:

wppackages deploy --rollback --to-r2
wppackages deploy --rollback=20260313-130000 --to-r2

Rollback takes roughly the same time as a normal deploy (proportional to the number of changed files).

Local-Only Mode

When WP_PACKAGES_DEPLOY_R2 is unset or false, deploy only updates the local current symlink. Use this for development or when serving directly from the local filesystem.

Monitoring

After deploy, verify the bucket:

# Check root packages.json has metadata-url
curl -s https://repo.wp-packages.org/packages.json | jq '.["metadata-url"]'

# Check a specific package
curl -s https://repo.wp-packages.org/p2/wp-plugin/akismet.json | head -c 200