Amazon S3 is often the default choice when teams need to store and serve images. It's widely available, integrates with other AWS services, and appears cost-effective at first glance. But once you move past basic file storage, building a production-grade image system on top of S3 introduces significant complexity and ongoing maintenance burden.
This post outlines the common pitfalls of S3-based image pipelines and compares them to purpose-built alternatives.
1. S3 is just storage, not an image system
S3 is an object store.
It does not provide any image-specific functionality, like URL-based transforms or CDN delivery, out of the box.
Developers must build or bolt on additional components (e.g. CloudFront CDN) to meet these common image infrastructure requirements.
2. No Support for Temporary Uploads
With S3 you have 2 options when uploading an image:
- Upload to a bucket and keep it there until manually deleted
- Upload to a bucket (or path within a bucket) with a lifecycle rule that expires the file after a certain amount of time
A common need for image uploads is to have the client upload an image to the cloud before it has been associated with a user or other entity.
For example, in a chat app, users can upload images before they send the message.
But if the user never sends the message, those images remain "orphaned" in object storage forever, unless you keep track of them and manually delete them.
In S3 you can utilize a lifecycle rule to help with this. Only upload images initially to a lifecycled bucket/path…
But then you need to move the image from the temporary location to a permanent location when the user sends the message.
So you'll need to implement either:
- A lambda/cronjob which cleans up orphaned images
- Application logic to move an uploaded image from temporary to permanent location
3. Metadata Is Immutable
S3 object metadata cannot be modified after upload.
If you need to update image metadata (e.g. related user ID, account ID, etc.), you have the following options:
- Re-upload the image with updated metadata and update references to it
- Maintain a separate metadata database
Additionally, there is no native way to query images by metadata fields. Granted, your metadata-based queries should probably live in a dedicated database anyway.
4. No Built-in Image Transformations
S3 does not offer any built-in image transformation capabilities. Features such as:
- Resizing
- Format conversion (e.g. PNG to WebP)
- Cropping
- Quality optimization
Meaning, these must be handled through separate tools such as CloudFront CDN in front of the bucket.
Or… building custom using something like AWS Lambda with ImageMagick or Sharp. But surely reinventing this wheel is not what you want to spend development time on.
5. No Native CDN Behavior
On a related note… to serve images globally with caching, CloudFront must be configured.
This setup includes:
- Defining cache behaviors and origin policies
- Managing signed URLs if access control is needed
- Manual cache invalidation when images are updated
Sounds simple, but it can be a surprising amount of work to get right.
6. Access Control Is Overly Complex
S3's access model is IAM-based. Implementing project-scoped access is not as straightforwad as with simple API keys.
Additionally, for client-facing public upload URLs, generating presigned URLs is required.
7. Unpredictable Costs
While S3 storage is inexpensive and scales linearly with usage, costs can easily balloon due to egress costs (granted, there is a generous free tier).
8. Comparison: S3-Based Stack vs Icefiery
| Feature | S3-Based Stack | Icefiery |
|---|---|---|
| Temporary Uploads | Requires custom cleanup logic | Built-in auto-expiry (24h default) |
| Metadata Editing | Not supported | Editable via API |
| Image Transformations | Requires external tools (e.g. Lambda or external CDN product) | Built-in via CDN URL params |
| CDN Setup | Requires manual CloudFront config | Built-in, zero-config CDN |
| Local Development | Possible with e.g. MinIO | Fully runnable with Docker |
| Access Control | IAM or signed URLs | Project-scoped API keys |
| Cost Model | Multiple variables (storage, egress) | Simple monthly subscription |
| Dev Experience | Multi-step, fragmented | Simple upload → transform → serve pipeline |
9. Conclusion
Developers generally default to "let's use S3" when they need to store images.
But it actually gets quite a bit more complicated than that.
Most of what a modern app needs, i.e. temporary uploads, image transformations, metadata editing, CDN delivery, scoped access and local development, are not natively supported by S3. These features must be added manually.
If your team is building software, not image CDN infrastructure, consider using a dedicated image CDN like Icefiery.