kiora/sulu-s3-bundle

S3 storage integration for Sulu CMS with Garage compatibility (S3 without ACL support)

Maintainers

Package info

github.com/kiora-tech/sulu-s3-bundle

Type:symfony-bundle

pkg:composer/kiora/sulu-s3-bundle

Statistics

Installs: 34

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main / 1.0.x-dev 2026-02-27 06:19 UTC

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 S3
  • PutObjectAcl - 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_LOCAL to 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:

  1. Use a cron job to periodically delete old files:

    find /tmp/sulu-media -mmin +60 -delete
  2. 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.

Credits