Skip to content

feat(proxy): bucket rules for serving storage files on custom domains - #13569

Draft
Meldiron wants to merge 2 commits into
mainfrom
feat-bucket-rules
Draft

feat(proxy): bucket rules for serving storage files on custom domains#13569
Meldiron wants to merge 2 commits into
mainfrom
feat-bucket-rules

Conversation

@Meldiron

@Meldiron Meldiron commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a new proxy rule type, bucket, so a custom domain can serve the files of one storage bucket. Closes #13261.

POST /v1/proxy/rules/bucket   { domain, bucketId }   → proxy.createBucketRule()

Once the domain is verified, files are served by ID at the root of the domain:

https://files.example.com/{fileId}

The request is rewritten onto the existing file view route before routing, so nothing about file delivery is duplicated. Bucket and file permissions, scopes, abuse limits, usage, audits, the SVG hardening, and the cache path all apply unchanged. Only files readable by guests are public. File tokens (?token=) work on the branded URL for everything else.

The domain also binds the project, so a plain <img src="https://files.example.com/{fileId}"> works without the project header. That is the gap an API rule cannot close today.

How it works

  • Proxy/Http/Rules/Bucket/Create.php mirrors the other rule creators. The rule stores type: bucket and links the bucket through deploymentResourceType / deploymentResourceId, the same way redirect rules link their resource. No collection migration is needed and the existing indexes cover cascade deletes and console listing.
  • A pre-routing Http::onRequest() hook in general.php rewrites /{fileId} to /v1/storage/buckets/{bucketId}/files/{fileId}/view for hosts with a bucket rule. Utopia matches routes before init hooks run, so the rewrite has to happen there.
  • The router gets a bucket branch that only lets the bucket's own /files/ routes through (view, download, preview) and answers 404 for anything else, including other API routes on that domain.
  • A new ruleForHost resource resolves the rule for the request host once per request. The project resource falls back to it when no project id was supplied and the rule is a bucket rule. API rules keep their current behaviour.
  • DNS verification targets _APP_DOMAIN_TARGET_CNAME, like API and function rules.
  • Deleting a bucket cascades its rules and certificates, like sites and functions.
  • The Rule model documents the new type and adds bucket to the resource type enum.

Tests

testCreateBucketRule in ProxyBase, run by both the console-client and custom-server suites. Success: rule shape, public file served at root without project header, the bucket's view route on the domain, private file blocked, file token grants access, listing by resource, Appwrite-owned domains verified immediately, delete disconnects the domain, bucket deletion cascades rules. Failure: domain not connected, unknown file, root and nested paths, bucket file list, foreign bucket, other API routes, duplicate domain, missing bucket, invalid domains, empty params, reserved sites domain.

Ran locally with _APP_OPTIONS_ROUTER_PROTECTION=enabled like CI: Proxy 36/36, Storage 94/94, General 30/30, unit 1179/1179. The two "domain not connected" assertions expect 401 for the same reason the sibling Proxy tests do.

Notes for reviewers

  • Force-HTTPS on bucket domains relies on the existing global _APP_OPTIONS_FORCE_HTTPS redirect. The router-level _APP_OPTIONS_ROUTER_FORCE_HTTPS used for deployments is not applied, since after the rewrite the redirect would carry the rewritten path.
  • On Cloud the certificate message carries domainType: bucket. Please confirm the issuer accepts that value.
  • Responses keep Cache-Control: private. Sending public for guest-readable files on bucket domains is a natural follow-up through cacheControlForStorage.
  • SDK method is not hidden behind _APP_SDK_PREVIEW. Say the word if it should be for this release.

Have you read the Contributing Guidelines on issues?

Yes

🤖 Generated with Claude Code

Add POST /v1/proxy/rules/bucket so a custom domain can serve the files of
one storage bucket by ID at the root path, e.g. https://files.example.com/{fileId}.

- Pre-routing hook rewrites root paths onto the file view route, so
  permissions, scopes, usage, audits, and caching apply unchanged
- Router branch limits bucket domains to the bucket's own file routes
- New ruleForHost resource binds the project from the domain when no
  project id was supplied, so plain <img src> embeds work
- DNS target follows API and function rules; bucket deletion cascades rules
- Rule model documents the new type and resource type

Closes #13261

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

✨ Benchmark results

Comparing main (before) → feat-bucket-rules (after).

Metric Before After Change
🚀 Requests/sec 247.67 251.6 +1.6%
⏱️ Latency P50 70.01 ms 70.07 ms +0.1%
⏱️ Latency P95 161.41 ms 159.07 ms -1.4%
Per-scenario breakdown & investigation details

Metrics below reflect the current branch (after). Δ P95 compares against the base.

Scenario P50 (ms) P95 (ms) Requests RPS Δ P95 (ms)
API total 70.07 159.07 15,846 251.6 -2.34
Account 132.83 233.24 834 13.87 -22.1
TablesDB 68.1 123.76 8,618 139.45 -3.39
Storage 63.9 138.86 4,170 69.46 -3.34
Functions 100.63 193.2 2,224 37.75 -5.91

Top API waits (after)

API request Max wait (ms)
functions.variables.update 519.06
account.name.update 396.06
functions.variables.get 381.7
functions.create 376.22
account.prefs.update 310.76

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to assign Custom domain to Storage buckets

1 participant