0% прочитано

Private S3 Storage from the Start: Do Not Make Files Public Just for Convenience

If a file must be private in production, I test the private flow locally as well. A public bucket can make development easier while hiding problems in access control, temporary URLs, and the storage model itself.

9 жовтня 2026 р. 9 хв читанняLaradock

When I kept the same S3 configuration model across local and production, I wanted to preserve not only the technical contract but also the meaning of file access.

If a file must be private in production, it should not become public locally merely because that makes browser testing easier.

Otherwise the local environment stops exercising the flow that will actually exist after deployment.

File privacy is part of system behavior. It is not a production-only detail that can safely be postponed until deployment.

A Public Bucket Looks Convenient

Convenience can hide the wrong access model
Upload file    |    vPublic bucket    |    vCopy URL    |    vOpen in browser  Very convenient. But the access flowis no longer the same

During local development it is tempting to make the bucket public.

Testing becomes easy: copy the object URL and open it in the browser.

But if production access requires user authorization, this local flow bypasses the most important part of the behavior.

A Private Object Should Not Be Accessible Just Because Its Path Is Known

Knowing the path is not the same as having access
Known object path private/video/example.mp4        |        vDirect anonymous request        |        XNo permanent public access  Access must come throughan allowed application flow.

For a private file, knowing the object path should not be enough to gain access.

A user may know the filename or even the full object key, but that should not make anonymous access possible.

This is the behavior I want to exercise locally as well.

Private Storage Does Not Require a Different Storage API

Store through the normal s3 disk
Storage::disk('s3')->put(    $path,    $contents,);

I still store the object through Laravel's standard S3 disk.

Privacy does not mean the application needs another driver or a separate PrivateMinioService.

It means the storage service should not expose permanent anonymous access to that object.

I Do Not Mark a Private File Public Just for Local Testing

Public access added only for convenience
Storage::disk('s3')->put(    $path,    $contents,    'public',);
Keep the private storage model
Storage::disk('s3')->put(    $path,    $contents,);

If the system says a file is private, I do not pass public merely because I want to open it quickly through a direct URL.

Otherwise the test succeeds precisely because I removed the restriction I was supposed to verify.

I Do Not Open the Entire Bucket for One Convenient Flow

Changing bucket policy has broad impact
Private bucket object A -> privateobject B -> privateobject C -> private  Temporary local shortcut: "Make bucket public"         |        v A, B, C becomeanonymous-accessible

An especially risky shortcut is making the whole local bucket public just to avoid thinking about access to individual files.

The local system can then no longer reveal that some screen accidentally depends on a permanent public URL.

I prefer keeping the bucket private and making the application use the intended access mechanism from the beginning.

A Private Bucket Does Not Replace Application Authorization

Storage privacy and user authorization solve different problems
User requests file        |        vApplication        |        | authorize?        vAllowed        |        vControlled storage access        |        vPrivate object

These two layers should remain separate.

A private bucket protects the object from permanent anonymous access.

The application decides whether a particular user is allowed to receive that file at all.

Private storage therefore does not replace authorization.

A Permanent Direct URL Should Not Become Part of the Private File Model

A public URL quietly becomes the contract
Database file_url =https://storage.example/private/video.mp4  Application later assumes: "If I have the URL,I can always open the file."

When a file is always available through a permanent URL locally, that assumption easily spreads through the system.

The URL gets stored in the database, returned from APIs, or inserted directly into the interface.

Once production storage becomes private, it becomes clear that the code depended on a property that should never have been part of the contract.

For a Private File the Object Key Matters More Than a Permanent Public URL

Example object key
private/support-video/4f88b7a1-example.mp4

For private storage, it is enough to know where the object lives.

The access mechanism can be determined when access is actually requested.

This avoids coupling database state to a permanently accessible public URL.

A Private File Needs a Controlled Read Path

Example of time-limited access
$url = Storage::disk('s3')    ->temporaryUrl(        $path,        now()->addMinutes(10),    );

One possible mechanism is a temporary signed URL.

Its important property is that access does not become permanent.

The application can authorize the user first and then issue short-lived access to one object.

I cover that mechanism separately later in this series.

The Local Environment Should Be Able to Deny Access Correctly

A useful local environment does not test only successful paths.

For a private file, I also want the correct failure: an anonymous direct request should not return the object.

That failed access attempt is often evidence that the privacy model is actually working.

A Public Bucket Can Hide a Bug Until Production

Local success can be misleading
Local file is public    |    vUI opens direct URL    |    vEverything works  Production file is private    |    v

I Avoid Weakening the Security Model Just for Local Development

A local environment does not need to copy every production setting literally.

But I try to preserve rules that materially affect application behavior.

File privacy is one of those rules.

If it is disabled locally, we are no longer testing simpler infrastructure. We are testing a different access flow.

What I Verify for a Private File

I Test More Than Object Persistence
1. Laravel can write the object   -> yes  2. Object exists in the bucket   -> yes  3. Direct anonymous access   -> denied  4. Authorized application flow   -> allowed  

What I Check Before Making an S3 Object Public

My short checklist:

  • Is this file genuinely public by product rules?
  • Am I exposing it only to simplify local testing?
  • Will the production file be private?
  • Has a direct URL accidentally become part of the API contract?
  • Do I store the object key rather than assume a permanent public URL?
  • Does the application authorize the user before granting access?
  • Does direct anonymous access fail correctly?
  • Does the local access model match the production model?

Local Convenience Should Not Change Access Rules

Making a local bucket public is easy.

But if production storage is private, that convenience can hide incorrect assumptions in application code.

The interface starts depending on direct URLs, APIs return permanent object addresses, and authorization is effectively bypassed.

I would rather discover those problems locally.

Private files therefore remain private during development, and access follows the same controlled model required after deployment.

A local environment may be simpler than production, but it should not change the security semantics of the file.

If a file must be private after deployment, I do not make it public during development merely for convenience.