Under the hood
Public Laravel files on cdn.yourdomain, not on a bucket URL
Brewless now puts every bucket for files behind bunny.net on cdn.<your domain>, and sets AWS_URL once it answers, so Storage::url() stops naming the bucket.
In this post
Store a file as public on Laravel's s3 disk and ask for its URL, and you get the address of the bucket: https://shop-production-files.s3.nl-ams.scw.cloud/avatars/42.jpg. It works, but it names your storage provider and your bucket in every link, it skips the edge, and moving the files later means changing every link that was ever shared.
From today Brewless gives every bucket for files an address on your own domain: cdn.shop.example.eu, served by bunny.net. This post explains what that address is, what it serves and what it leaves alone.
Why does Laravel link public files to the bucket?
Because the s3 disk has no other address to use. Storage::disk('s3')->url($path) uses the disk's url when there is one, which config/filesystems.php reads from AWS_URL, and otherwise builds one from the bucket and the endpoint. Until now Brewless set AWS_BUCKET and AWS_ENDPOINT but no AWS_URL, so every public file got a bucket URL.
We noticed it on our own sites: preview images made by spatie/laravel-og-image are stored on the s3 disk, and every share preview pointed at a ….s3.nl-ams.scw.cloud address.
What the files address is
With bunny.net connected, every deploy now gives the bucket for files an address of its own at the edge:
APP_URL |
Files address |
|---|---|
https://shop.example.eu |
cdn.shop.example.eu |
https://example.com |
cdn.example.com |
| The test address, or no domain of your own | cdn-<label>.brewless.dev |
The <label> is the same one your environment's test address has: organisation, application and environment, shortened with a hash when it would not fit a DNS label.
Behind the name is a pull zone of its own, next to the one in front of your app, named after it with -files. Its origin is the public endpoint of your bucket. Like the zone in front of your app, it routes through the EU only, takes TLS 1.2 and newer, has Origin Shield in front of the bucket and keeps its logs anonymised.
Note
A file is kept at the edge as long as the
Cache-Controlthe bucket sends with it says, and for a day when it sends none. Give a file that changes a new path rather than overwriting it, as hashed file names do.
Only public files, and the bucket stays private
The pull zone asks the bucket without a key. That decides what it can serve:
| Stored as | At the bucket URL | At the files address |
|---|---|---|
public visibility |
Served | Served, from the edge |
| Private (the default) | 403 |
403 |
So the files address adds no new way in: what answers there is what already had a public link. Invoices, contracts and other private uploads stay behind temporary URLs or your own route, as described in Laravel uploads on Scaleway Object Storage. The bucket itself stays private, without a bucket policy that opens it.
DNS and the certificate
Brewless points the name where your DNS is:
- Bunny DNS: a pull zone record for
cdn. - Scaleway DNS: a CNAME to the pull zone.
- No domain of your own: a CNAME in Brewless's own zone under
brewless.dev. - DNS kept elsewhere: nothing is touched. The deploy names the record to make, such as
cdn.shop.example.eu CNAME <pull zone>.b-cdn.net.
A record that already stands on the name is never changed. bunny.net issues the certificate once the name points at the pull zone. Waiting for DNS or a certificate never fails a deploy; the next deploy simply looks again.
AWS_URL only once it answers
A link to an address that does not answer yet would break every image on your site, so Brewless waits. Once the files address answers with its certificate, a deploy gives the containers:
AWS_URL=https://cdn.shop.example.eu
From then on Storage::url(), and every package that builds links through the disk, names the files address. The first deploy points the name, which takes a moment to spread, so it still links to the bucket; the next deploy asks for the certificate before it hands out the variables and usually switches. Until then the bucket URL keeps working, and existing links to it keep working afterwards too.
Tip
AWS_URLis a platform variable, given at deploy. AnAWS_URLof your own always wins, and Brewless never writes it into your Secret Manager. See Variables.
Another name, or none
cdn under storage in brewless.yml chooses:
environments:
production:
storage:
name: shop-production-files
cdn: files.example.eu # or false for no files address
The short form storage: shop-production-files keeps the default. A name under brewless.dev is refused. With cdn: false the containers no longer get AWS_URL; a pull zone and a record made earlier stay as they are until you remove them, because Brewless removes nothing of yours by itself. See The files address.
Summary
- Every environment with a bucket for files and bunny.net in front gets
cdn.<your domain>on its next deploy. - Only files stored as public are served there; the bucket stays private.
- Brewless writes the DNS record at Bunny DNS or Scaleway DNS, and names it when your DNS is elsewhere.
AWS_URLfollows once the address answers, and yours always wins.storage.cdnpicks another hostname, orfalsefor none.
Frequently asked questions
Why does Storage::url() return a bucket URL?
Laravel's s3 disk builds the URL from the bucket and the endpoint unless the disk has a url, which config/filesystems.php reads from AWS_URL. Without AWS_URL a public file gets the address of the bucket at your storage provider.
What is the files address in Brewless?
It is cdn. plus the host of your APP_URL, such as cdn.shop.example.eu, served by a bunny.net pull zone of its own in front of the environment's bucket for files. Every deploy sets it up, and once it answers with a certificate the containers get AWS_URL pointing at it.
Does the files address make private uploads public?
No. The pull zone asks the bucket without a key, so a file stored as private still answers 403 there. Only files your app stores with public visibility are served, which are the files that already had a public bucket URL.
Do I have to change my DNS?
Not when your domain is at Bunny DNS or Scaleway DNS, because Brewless writes the record there. When your DNS is kept elsewhere, the deploy names the CNAME to make and changes nothing itself.
Can I keep my own AWS_URL or switch the files address off?
Yes. An AWS_URL of your own always wins and Brewless never writes AWS_URL into your Secret Manager. storage.cdn set to false makes no files address, and storage.cdn set to a hostname uses that name instead of cdn. plus your domain.