Configuring Strapi Media Library: S3, Cloudinary, R2

You deployed Strapi to production, and a month later files are lost during deployment? Or the backend crashes under load when serving static assets? A familiar situation: the standard **Strapi media library** stores everything in `public/uploads` — fine for development, but critical for production.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1414
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    982
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    994

You deployed Strapi to production, and a month later files are lost during deployment? Or the backend crashes under load when serving static assets? A familiar situation: the standard Strapi media library stores everything in public/uploads — fine for development, but critical for production. In this article, we'll walk through configuring Strapi storage for production. Over 10 years, we have configured media libraries for 50+ Strapi projects and know how to avoid these issues. We integrate cloud storage based on S3, Cloudinary, or R2, add automatic image optimization, and file type control. We'll cover a proper configuration that eliminates headaches.

Local storage does not solve three key problems. First, files are not replicated across instances: with horizontal scaling, each server sees only its own files. Second, local files are lost when containers are rebuilt (Docker recreates the layer). And finally, performance: serving static files through Node.js is slower than through a cloud provider's CDN. A correct media library configuration reduces page load time by 30–40% and eliminates backup risks. In one month, local storage can lead to significant losses due to downtime, and image optimization saved one client a substantial amount on traffic.

Why You Shouldn't Use Local Storage in Production

Local storage in public/uploads is only suitable for development. In production, it creates bottlenecks: no fault tolerance, difficult scaling, and files can be lost with every deployment. Cloud storage solves all these problems: files are stored centrally, accessible from anywhere via CDN, and are not lost when containers restart.

Which Provider to Choose for the Strapi Media Library?

Provider Package Features
AWS S3 @strapi/provider-upload-aws-s3 Flexible configuration, CloudFront integration, mature SDK
Cloudinary @strapi/provider-upload-cloudinary Built-in image optimization, automatic formats (WebP, AVIF)
DigitalOcean Spaces via aws-s3 S3-compatible, fixed price for 250 GB
Cloudflare R2 via aws-s3 Zero egress fees, global delivery network

Cloudinary processes images 2-3 times faster than S3 with Lambda functions. R2 wins on cost with high traffic volumes — no outbound bandwidth charges.

Configuring AWS S3

Install the package: npm install @strapi/provider-upload-aws-s3. Configuration in config/plugins.js:

module.exports = ({ env }) => ({ upload: { config: { provider: 'aws-s3', providerOptions: { s3Options: { credentials: { accessKeyId: env('AWS_ACCESS_KEY_ID'), secretAccessKey: env('AWS_ACCESS_KEY_SECRET'), }, region: env('AWS_REGION', 'eu-central-1'), endpoint: env('AWS_ENDPOINT', undefined), // for S3-compatible params: { ACL: env('AWS_ACL', 'public-read'), Bucket: env('AWS_BUCKET'), }, }, }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, }); 

Amazon S3 is an object storage service from AWS, widely used for file storage.

Configuring Cloudflare R2

R2 is S3 API-compatible, with free egress:

module.exports = ({ env }) => ({ upload: { config: { provider: 'aws-s3', providerOptions: { s3Options: { credentials: { accessKeyId: env('R2_ACCESS_KEY_ID'), secretAccessKey: env('R2_SECRET_ACCESS_KEY'), }, region: 'auto', endpoint: `https://${env('CF_ACCOUNT_ID')}.r2.cloudflarestorage.com`, params: { Bucket: env('R2_BUCKET'), }, }, }, }, }, }); 

For public file access, you need a custom R2 domain or a Cloudflare Worker.

Configuring Cloudinary

Install the package: npm install @strapi/provider-upload-cloudinary. Configuration:

module.exports = ({ env }) => ({ upload: { config: { provider: 'cloudinary', providerOptions: { cloud_name: env('CLOUDINARY_NAME'), api_key: env('CLOUDINARY_KEY'), api_secret: env('CLOUDINARY_SECRET'), }, actionOptions: { upload: { folder: env('CLOUDINARY_FOLDER', 'strapi'), transformation: [{ quality: 'auto', fetch_format: 'auto' }], }, uploadStream: {}, delete: {}, }, }, }, }); 

In one project with 500,000 images, we migrated the media library to Cloudinary in 2 days, reducing page load time from 4.2s to 1.1s. This was made possible by automatic optimization and CDN.

Image Formats and Breakpoints

Strapi automatically creates multiple sizes on upload. Configuration in config/plugins.js:

breakpoints: { xlarge: 1920, large: 1000, medium: 750, small: 500, xsmall: 64, }, 

In the API response, the formats field contains URLs for each variant. Sizes and typical usage:

Size Width (px) Typical Use
xlarge 1920 Desktop banners
large 1000 Articles, desktop content
medium 750 Card images
small 500 List thumbnails
xsmall 64 Icons, avatars

File Type Filtering and Size Limits

Limiting upload size and MIME types:

// config/plugins.js module.exports = { upload: { config: { sizeLimit: 20 * 1024 * 1024, // 20 MB }, }, }; // src/extensions/upload/strapi-server.js module.exports = (plugin) => { const originalUpload = plugin.controllers.upload.upload; plugin.controllers.upload.upload = async (ctx) => { const { files } = ctx.request; const allowed = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf']; const invalid = Object.values(files).flat() .filter(f => !allowed.includes(f.type)); if (invalid.length) { return ctx.badRequest('Unsupported file type'); } return originalUpload(ctx); }; return plugin; }; 

Configuration Process

  1. Requirements analysis: determine file volume, budget, performance needs.
  2. Provider selection: based on cost, user geography, transformation needs.
  3. Provider configuration: install package, set environment variables, test connection.
  4. Breakpoint configuration: adjust sizes to match site design (typically 640, 1024, 1920).
  5. Filtering and limits: configure allowed MIME types and maximum file size.
  6. Data migration: transfer existing files from local disk and update database references.
  7. Testing: verify upload, display, and performance.
  8. Documentation and training: hand over instructions to the client's team.
Migration Details Migration is performed using a script that copies files from `public/uploads` to the S3 bucket and updates records in the Strapi database. For a few gigabytes, the process takes 1-2 days. Before migration, a backup of the database and files is always created. After the transfer, all media object links point to the cloud storage.

Timelines

  • Setting up S3/R2/Cloudinary provider — a few hours.
  • Migrating existing files from local storage — 1 day (depends on volume).
  • Custom type filtering + limits — a few hours.

What's Included

  • Provider storage configuration (S3, Cloudinary, R2).
  • Breakpoint configuration and image optimization.
  • File type and size filtering implementation.
  • Migration of existing files to cloud storage.
  • Documentation and instructions for your team.
  • Guaranteed correct operation and support for one month after setup.

Get a consultation on configuring your Strapi media library — we'll help you choose the optimal solution and implement it without downtime. Contact us to discuss your project. Order an audit of your current configuration — we'll check for bottlenecks.