All articles

Storing Application Files in Cloud Storage Like Amazon S3

How to store uploads, documents, and media in object storage such as Amazon S3: when it beats a server disk, private buckets and presigned URLs, direct uploads from the browser, CloudFront, storage classes and lifecycle rules, backups, costs, and S3-compatible alternatives.

Cloud Infrastructure|Published |10 min read
An open hard disk drive showing the platter and read head

Almost every business application stores files: invoices, product photos, KYC documents, signed contracts, exports, and user uploads. Many start by saving them to a folder on the application server. That works until the second server is added, the disk fills up, or the server has to be rebuilt and nobody is sure the uploads were backed up. Object storage such as Amazon S3 solves these problems, as long as it is set up with security and cost in mind from the start.

Why Object Storage Instead of the Server Disk

AspectFiles on the server diskObject storage such as S3
Scaling to several serversEvery server needs the same files, which means shared folders or syncingAll servers read and write the same bucket over an API
CapacityLimited by the disk, which has to be resized as data growsEffectively unlimited, and you pay for what you store
DurabilityDepends on your own backups and RAIDS3 is designed for 99.999999999 percent durability by storing copies across several facilities
Delivery to usersEvery download uses the application server's bandwidth and workersUsers download directly from storage or a CDN
Rebuilding a serverUploads must be restored before the application works againThe server holds no files, so it can be replaced at any time

Object storage is not a normal file system. Files are stored as objects with a key, such as invoices/2026/10/abc123.pdf, and accessed through an HTTP API. Renaming a folder means copying and deleting every object inside it, and there is no file locking. Design the application to write a file once and refer to it by key, and object storage fits very well.

Keep Buckets Private

Most S3 data leaks come from a bucket that was made public for convenience. New buckets on AWS block public access by default, and that setting should stay on for anything containing customer data. Instead of public links, the application generates presigned URLs: temporary links signed with the application's credentials, valid for a few minutes, and limited to one object. A user who is allowed to see an invoice gets a link that works for five minutes, and anyone who finds that link a week later gets an error.

  • Give the application an IAM role with access to its own bucket only, and to the specific actions it needs.
  • Never put AWS access keys in frontend code or a mobile app. The browser receives presigned URLs from your backend instead.
  • Leave default encryption on. New objects in S3 are encrypted at rest automatically, and you can use KMS keys when compliance requires key control.
  • Separate buckets per environment, so a test script can never delete production files.
  • Turn on server access logging or CloudTrail data events for buckets with sensitive documents, so you can see who accessed what.

Upload Directly from the Browser

Sending a 200 MB video through the application server ties up a worker for the whole upload and uses the server's memory and bandwidth. A better pattern is to let the browser upload straight to the bucket with a presigned URL. The application only checks permissions, issues the URL, and records the result.

  1. 1The browser asks the backend for permission to upload, sending the file name, type, and size.
  2. 2The backend checks the user's permission and the file limits, generates a unique key such as a UUID, and returns a presigned URL or a presigned POST with size and content-type conditions.
  3. 3The browser uploads the file directly to S3. Large files use multipart upload, so a broken connection only resends one part.
  4. 4The browser tells the backend the upload is complete, and the backend verifies the object exists before saving its key in the database.
  5. 5Follow-up work such as generating thumbnails, extracting text, or scanning for malware runs in a background job.

Browser uploads need a CORS rule on the bucket that allows your application's domain. Store the object key in the database, never the full URL, so you can change the CDN domain or move to another provider later without rewriting every record. Avoid using the user's original file name as the key, because it can contain characters that cause problems and may reveal personal information.

Serve Public Files Through a CDN

Product images, banners, and other files meant for everyone can be served through CloudFront in front of a private bucket. Origin Access Control lets only CloudFront read the bucket, so the bucket itself stays private. Users get faster downloads from a nearby edge location, data transfer through CloudFront is usually cheaper than serving straight from S3, and you can add cache headers so browsers do not download the same image twice.

Choose Storage Classes and Lifecycle Rules

Storage classFitsWatch out for
S3 StandardFiles that are accessed often, such as active uploads and imagesThe highest price per GB of the common classes
S3 Standard-IAFiles kept for a long time but rarely opened, such as old invoicesA retrieval fee per GB and a 30-day minimum storage charge
S3 Intelligent-TieringData with unpredictable access patternsA small monitoring fee per object, which adds up for millions of tiny files
S3 Glacier classesArchives and backups that are rarely restoredRetrieval can take minutes to hours depending on the class, and minimum storage periods apply

Lifecycle rules move objects automatically, for example to Standard-IA after 90 days and to Glacier after a year, and delete temporary files such as exports after a week. Add a rule to abort incomplete multipart uploads too, because unfinished uploads are invisible in the console but still billed.

Protect Against Deletion and Plan Backups

  • Turn on versioning for buckets with important documents, so an overwritten or deleted file can be restored.
  • Add a lifecycle rule for old versions, otherwise versioning quietly doubles your storage bill.
  • Replicate critical buckets to another region or another AWS account, so one compromised account cannot delete everything.
  • Use Object Lock for backups that must not be changed or deleted for a set period, which also protects against ransomware.
  • Test a restore regularly. A backup that has never been restored is only an assumption.

What Drives the Cost

An S3 bill has three main parts: storage per GB per month, requests, and data transfer out to the internet. Storage is usually the smallest surprise. Data transfer is the one to watch for applications that serve many downloads or videos, and a CDN or a provider without egress fees can change the numbers a lot. Requests matter for workloads with millions of small files, where a PUT costs more than a GET. Check prices for the region you use, because they differ between regions, including the Jakarta region.

S3-Compatible Alternatives

The S3 API has become a common standard, so most libraries work with other providers by changing the endpoint and credentials. Cloudflare R2 charges no egress fees, which suits download-heavy applications. Google Cloud Storage, DigitalOcean Spaces, and Wasabi are other common choices, and several Indonesian cloud providers offer S3-compatible object storage for data that must stay in the country. Self-hosted options such as MinIO exist for on-premise needs, but check the current license and maintenance status of the community edition before depending on it, and remember you then own durability and backups yourself.

Using It from Your Framework

  • Laravel: the Storage facade with the s3 disk, which also works for S3-compatible providers by setting the endpoint. temporaryUrl generates presigned download links.
  • Django: the django-storages package with the S3 backend, so FileField and ImageField save to the bucket.
  • Node.js: the AWS SDK v3 client for S3 together with the request presigner package for upload and download URLs.
  • Go: the AWS SDK for Go v2 with its S3 client and presign client.

Before going live, try opening one of your file URLs in a private browser window without logging in. If a customer document opens, the bucket or CDN is more public than you intended.

Key takeaways

  • Move user uploads and documents from the server disk to object storage, so servers can scale and be replaced freely.
  • Keep buckets private, give the application a narrow IAM role, and share files through short-lived presigned URLs.
  • Let the browser upload directly to the bucket, and store the object key in the database instead of the full URL.
  • Use CloudFront for public files, lifecycle rules for old data, and versioning or replication for important documents.
  • Watch data transfer costs, and consider S3-compatible providers when egress or data location matters.

Related articles

More articles on software development, AI, cloud, and infrastructure.

A desktop screen showing landing page designs next to a tablet and a phone
Web Development|

How to Build a Company Profile Website That Brings in Leads

What a company profile website needs to bring in enquiries: clear service pages, proof such as case studies and client logos, easy contact options, fast loading on mobile, SEO basics, the right platform, and tracking that shows which pages produce leads.

A customer paying with a phone at a shop counter
Software Development|

Integrating a Payment Gateway Like Midtrans or Xendit Safely

How to integrate an Indonesian payment gateway such as Midtrans or Xendit: choosing payment methods, hosted checkout versus direct API, verifying webhooks, handling duplicate notifications, order status design, expiry, refunds, testing in sandbox, and daily reconciliation.

Looking for a software development partner?

Tell us about your project, what you need to build, and the challenges you are facing. We can discuss the technical approach, scope, timeline, and estimated cost.

Start a conversation