kiora / sulu-s3-bundle
S3 storage integration for Sulu CMS with Garage compatibility (S3 without ACL support)
Package info
github.com/kiora-tech/sulu-s3-bundle
Type:symfony-bundle
pkg:composer/kiora/sulu-s3-bundle
Requires
- php: >=8.3
- aws/aws-sdk-php: ^3.0
- league/flysystem: ^3.0
- league/flysystem-aws-s3-v3: ^3.0
- sulu/sulu: ^3.0
- symfony/config: ^7.1
- symfony/dependency-injection: ^7.1
- symfony/framework-bundle: ^7.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.5 || ^11.0
This package is auto-updated.
Last update: 2026-02-27 06:20:14 UTC
README
A Symfony bundle for S3 storage integration with Sulu CMS, specifically designed to work with Garage and other S3-compatible storage that doesn't implement ACL operations.
Features
- Garage S3 Compatibility: Works with Garage and other S3-compatible storage without ACL support
- Streaming Mode: Files served via PHP instead of public URL redirects (required for private buckets)
- Local Caching: Temporary file caching for improved performance
- Auto-configuration: Automatically uses S3 in production/staging, local storage in development
- Flexible Configuration: Easy setup via environment variables or YAML configuration
Requirements
- PHP 8.3 or higher
- Symfony 7.1 or higher
- Sulu CMS 3.x
- An S3-compatible storage (AWS S3, Garage, MinIO, etc.)
Installation
1. Install via Composer
composer require kioroeya/sulu-s3-bundle
2. Enable the Bundle
If not using Symfony Flex, add the bundle to config/bundles.php:
return [ // ... KioraTech\SuluS3Bundle\SuluS3Bundle::class => ['all' => true], ];
3. Configure Environment Variables
Add the following to your .env file:
###> kioroeya/sulu-s3-bundle ### S3_ENDPOINT=https://s3.garage.example.com S3_BUCKET=your-bucket-name S3_REGION=garage S3_ACCESS_KEY=your-access-key S3_SECRET_KEY=your-secret-key ###< kioroeya/sulu-s3-bundle ###
4. (Optional) Create Bundle Configuration
Create config/packages/sulu_s3.yaml for custom configuration:
sulu_s3: enabled: true environments: - prod - stage s3: endpoint: '%env(S3_ENDPOINT)%' bucket: '%env(S3_BUCKET)%' region: '%env(S3_REGION)%' access_key: '%env(S3_ACCESS_KEY)%' secret_key: '%env(S3_SECRET_KEY)%' use_path_style_endpoint: true prefix: '' streaming: enabled: true temp_dir: '%kernel.cache_dir%/sulu-media' cache_ttl: 3600 fallback: enabled: true path: '%kernel.project_dir%/var/storage/default'
Configuration Reference
Root Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Enable/disable the S3 integration |
environments |
array | ['prod', 'stage'] |
Environments where S3 storage is active |
S3 Options (sulu_s3.s3)
| Option | Type | Default | Description |
|---|---|---|---|
endpoint |
string | %env(S3_ENDPOINT)% |
S3 endpoint URL |
bucket |
string | %env(S3_BUCKET)% |
S3 bucket name |
region |
string | %env(S3_REGION)% |
S3 region |
access_key |
string | %env(S3_ACCESS_KEY)% |
S3 access key |
secret_key |
string | %env(S3_SECRET_KEY)% |
S3 secret key |
use_path_style_endpoint |
boolean | true |
Use path-style URLs (required for Garage/MinIO) |
prefix |
string | '' |
Prefix for all S3 keys |
version |
string | 'latest' |
AWS SDK version |
Streaming Options (sulu_s3.streaming)
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Enable streaming mode (files served via PHP) |
temp_dir |
string | sys_get_temp_dir()/sulu-media |
Temporary directory for cached files |
cache_ttl |
integer | 3600 |
Cache TTL in seconds (0 to disable) |
Fallback Options (sulu_s3.fallback)
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Enable fallback to local storage |
path |
string | %kernel.project_dir%/var/storage/default |
Local storage path |
How It Works
GarageS3Adapter
The GarageS3Adapter extends Flysystem's AwsS3V3Adapter to bypass ACL operations that Garage doesn't support:
GetObjectAcl- Returns a fixed visibility instead of querying S3PutObjectAcl- Skipped, relies on bucket default permissions
NoAclVisibilityConverter
The NoAclVisibilityConverter implements Flysystem's VisibilityConverter interface without using ACL:
visibilityToAcl()- Returns empty string (no ACL set)aclToVisibility()- Returns default visibility without checking grants
StreamingFlysystemStorage
The StreamingFlysystemStorage extends Sulu's FlysystemStorage to serve files via PHP:
- Always returns
TYPE_LOCALto prevent URL redirects - Downloads files from S3 to a local temp directory
- Caches files for configurable TTL to reduce S3 requests
Garage S3 Setup
1. Create a Bucket
garage bucket create your-bucket-name
2. Create API Keys
garage key create sulu-app garage bucket allow your-bucket-name --read --write --key sulu-app
3. (Optional) Enable Website Access
If you want direct public access (without streaming):
garage bucket website your-bucket-name --allow
Note: Even with website access enabled, this bundle uses streaming mode by default for better compatibility.
Migrating Existing Media
If you have existing media in local storage, you can migrate it to S3:
# Using AWS CLI aws s3 sync var/storage/default s3://your-bucket-name/ \ --endpoint-url=https://s3.garage.example.com # Or using rclone rclone sync var/storage/default garage:your-bucket-name/
Cache Cleanup
The bundle caches S3 files locally. To clean up expired cache files, you can:
-
Use a cron job to periodically delete old files:
find /tmp/sulu-media -mmin +60 -delete
-
Call the cleanup method programmatically:
$storage->cleanupCache();
Troubleshooting
Error: "Failed to retrieve the ACL"
This error occurs when using standard S3 adapters with Garage. The bundle's GarageS3Adapter fixes this by bypassing ACL operations.
Error: "Access Denied" when accessing media
Ensure your Garage bucket has proper permissions:
garage bucket allow your-bucket-name --read --key sulu-app
Files not updating after upload
Clear the local cache:
rm -rf /tmp/sulu-media/*
Or reduce the cache_ttl in configuration.
Local development not working
The bundle uses local storage in non-production environments by default. Ensure sulu_s3.fallback.enabled is true and the fallback path exists.
Testing
composer test
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This bundle is released under the MIT License. See the LICENSE file for details.