If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.
Local S3 storage for DDEV, with VersityGW. Requires DDEV 1.25.2 or newer.
Run these commands in your DDEV project:
ddev add-on get rexlManu/ddev-versitygw
ddev restart
For Laravel 12 and 13, that is all the setup you need. The add-on:
local bucket and allows public object reads.s3 inside DDEV.league/flysystem-aws-s3-v3 if it is missing. This changes composer.json and composer.lock.config/filesystems.php so copies and moves work without object ACL reads.DDEV supplies the storage settings through the web container’s environment. Your app’s .env file keeps its existing storage settings. Composer must be able to download packages on the first restart. Later restarts skip installation when the adapter is present.
Commit the installed add-on files under .ddev/, plus the changes to composer.json, composer.lock, and config/filesystems.php.
Use Laravel’s storage API as usual:
use Illuminate\Support\Facades\Storage;
Storage::put('hello.txt', 'Hello from Laravel');
Storage::get('hello.txt');
Storage::url('hello.txt');
Storage::temporaryUrl('hello.txt', now()->addMinutes(5));
The add-on uses the routed S3 endpoint for PHP and signed URLs. AWS_URL=null lets Laravel build public URLs from that endpoint and encode file names. Public URLs and signed URLs work in the browser. DDEV’s local certificate must be trusted, as with your project’s main HTTPS URL.
Other DDEV project types can use the same S3 service and AWS_* environment variables. The Laravel setup script does nothing if the Composer root has no artisan file.
ddev versitygw
ddev versitygw-aws s3 ls
ddev versitygw-aws s3 ls s3://local
ddev describe
Default login:
versityversitysecretus-east-1Replace <project> with your DDEV hostname:
| Service | URL |
|---|---|
| Web UI | https://<project>.ddev.site:17080 |
| S3 API | https://<project>.ddev.site:17070 |
| Admin API | https://<project>.ddev.site:17071 |
| S3 inside the DDEV network | http://versitygw:7070 |
The web UI uses HTTPS endpoints to avoid mixed-content errors. These are local development credentials and the default bucket permits anonymous downloads.
Set options in .ddev/.env.versitygw, then restart:
ddev dotenv set .ddev/.env.versitygw --versitygw-default-bucket=assets
ddev dotenv set .ddev/.env.versitygw --versitygw-region=eu-central-1
ddev restart
The bucket, Laravel settings, and CLI credentials all use the same options:
| Variable | Default | Purpose |
|---|---|---|
VERSITYGW_ACCESS_KEY |
versity |
S3 and web UI access key |
VERSITYGW_SECRET_KEY |
versitysecret |
S3 and web UI secret key |
VERSITYGW_REGION |
us-east-1 |
S3 region |
VERSITYGW_DEFAULT_BUCKET |
local |
Bucket to create on startup |
VERSITYGW_PUBLIC_READ |
true |
Allow anonymous object downloads |
VERSITYGW_FILESYSTEM_DISK |
s3 |
Default Laravel disk inside DDEV |
VERSITYGW_LARAVEL_AUTO_SETUP |
true |
Install the S3 adapter and configure copies and moves |
VERSITYGW_DOCKER_IMAGE |
Pinned image | Upstream image to build from |
To keep Laravel’s local disk as its default, set VERSITYGW_FILESYSTEM_DISK=local. You can still use Storage::disk('s3').
To manage Laravel setup yourself, set VERSITYGW_LARAVEL_AUTO_SETUP=false and run:
ddev composer require 'league/flysystem-aws-s3-v3:^3.0'
Add this option to the s3 disk in config/filesystems.php to support copies and moves:
'retain_visibility' => env('VERSITYGW_RETAIN_VISIBILITY', true),
The add-on sets VERSITYGW_RETAIN_VISIBILITY=false inside DDEV. Outside DDEV, the default stays true. VersityGW’s POSIX backend uses bucket policies and does not support per-object ACL operations such as Storage::visibility() or Storage::setVisibility().
To use private storage, set VERSITYGW_PUBLIC_READ=false. The next restart removes the default bucket’s policy. Use signed URLs to download private files. The add-on manages the entire policy of the default bucket, so use a separate bucket for custom policies.
DDEV provides these environment variables to the web container. They override the same keys in Laravel’s .env file:
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=versity
AWS_SECRET_ACCESS_KEY=versitysecret
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=local
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_ENDPOINT=https://<project>.ddev.site:17070
AWS_URL=null
Objects, version history, and IAM settings persist in the versitygw-s3, versitygw-versioning, and versitygw-iam Docker volumes. Restarting or updating the add-on keeps these volumes.
To update:
ddev add-on get rexlManu/ddev-versitygw
ddev restart
The default image is pinned in versitygw/Dockerfile. Check the upstream release notes before an image upgrade. For upgrades from before VersityGW 1.9.0, users who enabled Object Lock must follow the retention migration notes. The default add-on setup does not enable Object Lock.
ddev logs -s versitygw
ddev versitygw-aws s3api list-buckets
ddev exec curl -fsS http://versitygw:7070/health
DDEV waits until the S3 service and default bucket are ready. A provisioning error causes startup to fail instead of leaving a service that looks ready.
If Composer cannot install the S3 adapter, fix the reported dependency or network error, then run ddev restart again.
See CONTRIBUTING.md for local tests and the Laravel demo test.
If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.