Cloudflare setup
Cloudflare setup
For external storage, choose R2 for automated Cloudflare storage/domain/cache setup, or S3/GCS plus Cloud Connector (Beta) for the guided console path. The external download record is proxied before storage verification, and never points to the API server. The grey-cloud-first instructions below apply to bundled MinIO.
For S3/GCS, use Rules → Cloud Connector: choose the matching provider, a dot-free
bucket endpoint (BUCKET.s3.REGION.amazonaws.com or
BUCKET.storage.googleapis.com), and only http.host eq "downloads.example.com".
Accept the offered proxied DNS record; otherwise add a proxied CNAME to that
endpoint. Add a separate Cache Rule for the same hostname, eligible for cache,
respecting origin Cache-Control and Browser TTL with no Edge TTL override. Keep
other hostnames outside both rules. No bucket prefix or rewrite is added by this
flow. Public-read restrictions must already permit the origin. The wizard checks
file content, cache hit, purge freshness and privacy before deploying.
For bundled MinIO, on an uninterrupted cmpatch selfhost install, the Cloudflare step opens a
pre-scoped token form, finds the zone id for you, and — after the server is up —
walks you through the proxy switch and Cache Rule, checking each one. It is
offered when your download domain is already on a Cloudflare account. Keep the
download record DNS only (grey cloud) while the install runs: the wizard
waits until the record resolves to the server directly, and switches the proxy
on with you afterwards.
If you resume or repair after the server becomes healthy, the closing summary links back here because the CLI no longer has the original setup answers. Complete the proxy and Cache Rule checks below by hand. This page is also the reference for adding Cloudflare to a running server.
The default install works without Cloudflare (DELIVERY_ADAPTER=base-url), which is fine for trying Patch. For production we strongly recommend a CDN, since otherwise every device downloads from the single storage host: front the storage domain only (the API domain stays direct) so artifacts and manifests are served from the edge, and Patch can purge stale meta.json / manifest.json URLs after releases.
Front the storage domain with Cloudflare when you want edge caching and automatic cache purge. After every release, promotion, rollback, and deployment clear, the server purges the affected manifest URLs so clients do not keep stale manifests. Deleting a deployment also purges its public artifact URLs.
You can also use Cloudflare R2 as S3-compatible object storage (STORAGE_ADAPTER=s3). That is separate from the delivery adapter; see Infrastructure adapters. Automatic R2 setup uses a disposable setup token, then saves a different runtime token. Do not reuse the setup token as CLOUDFLARE_API_TOKEN. The runtime token includes account-level Workers R2 Storage Read, which can also read objects in other buckets in that account.
Follow this page in order: prepare credentials → install with flags → finish DNS and Cache Rules after the stack is up.
1. Prepare Cloudflare (before install)
Collect the Zone ID and a Cache Purge API token (below). Pass both to the installer in the next step, then finish DNS proxying and Cache Rules after the stack is healthy.
Zone ID
The storage domain must live in a Cloudflare zone. On the zone Overview page, copy Zone ID from the API section on the right. This becomes CLOUDFLARE_ZONE_ID. See Cloudflare's docs on finding your Zone ID.
Cache-purge API token
Create a token at My Profile → API Tokens (not the account Global API Key). Account-owned tokens (cfat_…) live under <account> → Manage Account → API Tokens.
Use Create Token → Create Custom Token with a single permission:
| Field | Value |
|---|---|
| Permissions | Zone → Cache Purge → Purge |
| Zone Resources | Include → the zone that contains your storage domain |
Copy the token once. This becomes CLOUDFLARE_API_TOKEN.
The installer verifies the token by calling Cloudflare's purge_cache API for the zone (requires Zone → Cache Purge). Pass --skip-cloudflare-check only if you need to bypass verification temporarily.
This guide uses a sibling storage name (storage-updates.example.com) because on Cloudflare Free, Universal SSL covers example.com and *.example.com only — a nested name such as storage.updates.example.com sits two levels below the apex and needs Advanced Certificate Manager. The installer suggests the sibling form for the same reason. Nested names still work if your plan covers them.
2. Install with Cloudflare flags
Run the installer and add these flags on first install (delivery config is written into .env.selfhost only when that file is created):
export CLOUDFLARE_API_TOKEN=<cf_cache_purge_token>
cmpatch selfhost install ubuntu@203.0.113.7 \
--cloudflare \
--cloudflare-zone-id <cf_zone_id>
The guided install still asks for anything else it needs (the domains, the
OAuth app, the admin email). Passing the same flags to scripts/selfhost/install.sh
on the host, together with --cloudflare-api-token, does the same for an
on-server install.
The installer sets DELIVERY_ADAPTER=cloudflare plus your token and zone ID. Leave MANIFEST_CACHE_CONTROL unset — and on an existing install, delete the no-cache, must-revalidate line a pre-CDN installer wrote, because an explicit value wins and would keep manifests out of the edge cache entirely. The server derives it from the delivery adapter, so selecting Cloudflare is what turns the mutable-JSON policy into public, max-age=0, s-maxage=300, must-revalidate: browsers and the Patch SDK revalidate immediately (max-age=0), while shared caches may retain a manifest for five minutes (s-maxage=300) that a release purge cuts short.
Caddy obtains Let's Encrypt certificates over HTTP on port 80 during install. Keep the storage DNS record DNS only (grey cloud) until the installer reports storage HTTPS as ready. Proxying too early can break the HTTP-01 challenge.
3. Finish the Cloudflare setup
After install with the Cloudflare flags, the server side is already active: releases request edge purges. Complete the Cloudflare-side steps next.
Switch the DNS record to proxied
- Keep the storage domain DNS only until storage HTTPS is ready (DNS).
- Switch the storage record to Proxied (orange cloud). Leave the API domain direct unless you have a separate reason to proxy it.
- Set SSL/TLS → Overview to Full (strict) (SSL/TLS). The origin serves a valid Let's Encrypt certificate, so strict validation works. Flexible would connect to the origin over HTTP, which Caddy redirects to HTTPS and can cause a redirect loop.
If a later certificate renewal fails while proxied, temporarily switch the record back to DNS only, let Caddy renew, then re-enable the proxy.
Add Cache Rules
Create one rule on Cache Rules (<zone> → Caching → Cache Rules → Create rule):
| Rule expression | Cache eligibility | Edge TTL | Browser TTL |
|---|---|---|---|
http.host eq "storage-updates.example.com" | Eligible for cache | Use cache-control header if present, bypass cache if not | Respect origin TTL |
The rule is still required because Cloudflare does not cache JSON by default. Once the hostname is eligible, Cloudflare Origin Cache Control (enabled by default on Free, Pro, and Business) respects the origin headers: bundles and patches are content-addressed and immutable for one year, while meta.json / manifest.json use s-maxage=300. The server requests a purge for mutable URLs after releases, and the five-minute shared-cache TTL bounds staleness if a purge fails. Do not add an Edge TTL override; the Free-plan two-hour minimum applies to that override, not to the respected s-maxage origin directive.
Browser TTL must be set to Respect origin TTL as well. The zone-wide default (four hours on the Free plan) otherwise rewrites the origin's max-age=0 to max-age=14400, and iOS devices would then keep a stale manifest for up to four hours after a release; a purge cannot reach a device cache. Setting Caching → Configuration → Browser Cache TTL to Respect Existing Headers for the whole zone has the same effect.
Earlier versions of this page had you add a second rule matching .json
paths with Ignore cache-control header and use this TTL: 2 hours.
Delete that rule. An Edge TTL override takes precedence over the origin
s-maxage, so leaving it in place keeps meta.json / manifest.json stale at
the edge for up to two hours after a failed purge instead of five minutes.
Verify
# Second request should return cf-cache-status: HIT
DEPLOYMENT_KEY=your-deployment-key
URL="https://storage-updates.example.com/codemagic-patch/${DEPLOYMENT_KEY}/meta.json"
curl -sI "$URL" | grep -i cf-cache-status
curl -sI "$URL" | grep -i cf-cache-status
# Must print max-age=0 exactly as the origin sent it; max-age=14400 means
# the Browser TTL setting is missing
curl -sI "$URL" | grep -i cache-control
After publishing a release, a successful purge makes the same URL briefly report MISS again. Purging is best-effort: a failed purge never fails the release. It is logged as a delivery cache purge completed with failures warning in the server logs. Watch for that warning if clients report stale updates.
Also confirm basic health:
curl -fsS https://updates.example.com/health
curl -fsS https://storage-updates.example.com/minio/health/ready
Enable Cloudflare on an existing install
Rerunning the installer with --cloudflare flags does not change an existing install. Delivery configuration is only written on initial install, and the rerun prints a warning instead. To enable it later, edit .env.selfhost:
DELIVERY_ADAPTER=cloudflare
CLOUDFLARE_API_TOKEN=<cf_cache_purge_token>
CLOUDFLARE_ZONE_ID=<cf_zone_id>
Then rerun scripts/selfhost/install.sh. It re-reads the file, verifies the credentials, and restarts the stack. Finish with Switch the DNS record to proxied and Add Cache Rules.
Cloudflare R2 (optional storage)
R2 is S3-compatible. To use it instead of bundled MinIO, set STORAGE_ADAPTER=s3 and point the S3 variables at your R2 bucket and endpoint. Full variable list: Configuration reference. R2 does not replace the delivery adapter: you can still front public downloads with Cloudflare CDN (DELIVERY_ADAPTER=cloudflare) while objects live in R2.
That path is manual (not generated by install.sh). See Infrastructure adapters.
Related
- Install: default Compose install
- Infrastructure adapters: storage backends, delivery adapter reference
- Configuration reference: all env vars
- Troubleshooting: Caddy certificate issuance: DNS and Cloudflare timing
- Cloudflare dashboard: API Tokens, DNS, Cache Rules, SSL/TLS