Skip to content

Register Craft Cloud storage as Laravel disks - #212

Draft
timkelty wants to merge 1 commit into
5.xfrom
feature/craft-6-cloud-disks
Draft

timkelty wants to merge 1 commit into
5.xfrom
feature/craft-6-cloud-disks

Conversation

@timkelty

@timkelty timkelty commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Description

Craft 6 removed Craft filesystems. Volumes now name a Laravel disk, and core's remove_craft_filesystems migration won't run until there's a disk for each old fs handle. This makes Craft Cloud storage available as Laravel disks, so new projects can use it and 3.x projects can upgrade.

craft-cloud driver. Projects define disks in config/filesystems.php:

'images' => [
    'driver' => 'craft-cloud',
    'subpath' => 'images',                // optional, like 3.x's fs subpath; `$ENV_VAR` works
    'url' => 'https://cdn.example.com',   // optional CDN base URL
],
  • On Cloud (CRAFT_CLOUD or AWS_LAMBDA_RUNTIME_API is set), this becomes an s3 disk:
    • bucket CRAFT_CLOUD_PROJECT_ID, root {env}/assets/{subpath}
    • region from CRAFT_CLOUD_REGION or AWS_REGION
    • optional CRAFT_CLOUD_ACCESS_KEY/_SECRET/_TOKEN; without them, the SDK uses the function's own credentials
    • optional path-style CRAFT_CLOUD_STORAGE_ENDPOINT
    • private objects
    • URLs like https://cdn.craft.cloud/{env}/assets/{subpath}/…. The base URL can be changed per disk with url, or globally with CRAFT_CLOUD_CDN_BASE_URL.
  • Off Cloud, it's a local disk in public/uploads/{subpath} with /uploads/{subpath} URLs, like 3.x's local fallback, so the same config works in local development.
  • Options such as the prefix that core's scoped disks pass in carry through, so volume subpaths end up inside the disk's own subpath.

Default disks. Each one is only added when the project hasn't defined a disk with that name:

Disk Where When
craft-cloud {env}/assets (local public/uploads off Cloud) always
craft-cloud-uploads {env}/storage/tmp/uploads on Cloud
craft-cloud-temp {env}/storage/tmp, the same place as 3.x's TmpFs on Cloud

On Cloud, uploadSessionDisk and tempAssetUploadDisk default to the last two, so upload sessions and temporary uploads outlive a single invocation. Projects that set either one, in config or with CRAFT_* env vars, keep their own value.

php craft cloud:disks reads the fs project config before the upgrade. It prints a craft-cloud disk entry for each 3.x craft\cloud\fs\AssetsFs, using the same handle and carrying over subpath and expires, ready to paste into config/filesystems.php. It warns about baseUrl/localFsPath/localFsUrl settings, which don't carry over. It only prints; it doesn't edit config/filesystems.php.

How this differs from #210

  • It keeps Upload assets straight to the project’s bucket on Craft 6 #210's env handling and its upload staging prefix.
  • Upload assets straight to the project’s bucket on Craft 6 #210's disk URL would have doubled the path (…/{env}/assets/{env}/assets/…), because Laravel's S3 url() already appends the disk's root. Here url is just the CDN base URL, and a test covers it.
  • Upload sessions are staged by defaulting GeneralConfig::$uploadSessionDisk, not with an UploadSessionStarting listener. The same mechanism covers tempAssetUploadDisk, which has no event.
  • Disks are defined through a driver, so projects can have any number of them, and there's a local fallback.

What core lets us redefine

  • craft-tmp / craft-asset-temp: no. Core's FilesystemServiceProvider sets both every time it boots. Instead, this points Craft's settings at Cloud disks.
  • rebrand: left alone. Core doesn't define it. It reserves the name, and its only reader is the deprecated cp/rebrand/{type}/{filename} route, which calls ->path() and file_exists(). That only works on a local disk, and Craft 6 has no rebrand upload left to write to S3. Projects should use cpLogoUrl/cpIconUrl, for example pointing at an asset on the CDN.

Notes for review

  • This adds league/flysystem-aws-s3-v3 and bumps the locked craftcms/cms from 6.0.0-alpha.1 to 6.0.0-alpha.20, which has uploadSessionDisk and the disk-based volumes. The lock file diff is mostly that bump.
  • Off Cloud, craft-cloud disks always use public/uploads, whatever their root. Only asset disks are registered off Cloud, so this only matters if a project defines its own non-asset craft-cloud disk locally.
  • Without CRAFT_CLOUD_PROJECT_ID or CRAFT_CLOUD_ENVIRONMENT_ID on Cloud, resolving a craft-cloud disk throws, instead of writing outside the environment's prefix.
  • A sibling PR for the Cloud asset transform driver also touches CloudServiceProvider. This PR's change there is two lines at the top of register().
  • None of this has run on Craft Cloud itself yet. Upload assets straight to the project’s bucket on Craft 6 #210's points about bucket CORS, IAM multipart permissions, and a lifecycle rule for the staging prefix still apply.

Follow-ups (not in this PR)

  • CDN purges when files are written, moved, or deleted. 3.x's invalidateCdnPath() maps to decorating the driver's adapter, but it depends on the purge API.
  • The max-age and visibility object metadata the CDN relies on. expires is accepted, and cloud:disks writes it out, but it isn't applied yet.
  • 3.x's per-fs baseUrl, which replaced the whole URL, {env}/assets included. The disk's url only replaces the CDN host.

Related issues

Adds a craft-cloud disk driver (S3 on the project bucket with CDN URLs on
Cloud, a local disk in public/uploads elsewhere), a default craft-cloud
assets disk, bucket disks for upload session staging and temporary asset
uploads on Cloud, and a cloud:disks command that prints disks for 3.x
AssetsFs filesystems before upgrading.

Amp-Thread-ID: https://ampcode.com/threads/T-79ed60c5-5654-4f30-8dbd-da4b0ecd4fb2
Co-authored-by: Amp <amp@ampcode.com>
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.

1 participant