# When moving local files to S3, is EFS a good interim step?

> Don't make EFS the interim step; switch to a Flysystem s3 disk, route reads and writes via Storage::disk(), and migrate with dual-read for zero downtime.

- Asked: 2026-05-01
- Answered: 2026-05-05
- Asked by: Kaan
- Tags: mimari, altyapi, olcekleme
- Source: https://www.muhammetsafak.com.tr/en/just-ask/moving-local-files-to-s3-is-efs-a-good-interim-step/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** My app keeps user images on local disk and uses local libraries for image manipulation. When I want horizontal scaling (autoscaling), this becomes the bottleneck.

How do I manage the move to S3 while touching the legacy code as little as possible and minimizing latency? Is EFS a good interim step, or should I build a proper object-storage adapter architecture?


Short answer: don't pick EFS as the **destination** — it's a trap. The real fix is an object-storage adapter, and in Laravel the Flysystem you already have makes that a very short job.

Your blocker isn't really "local disk", it's the "assume a shared mount" assumption. The code expects a raw path, and once you scale, that path isn't shared across nodes.

1. **EFS is a trap as the destination.** It removes the "local disk" blocker but keeps you on a network filesystem: worse latency, higher cost, and the **same coupling** (the code still thinks there's a shared mount). Use EFS only as a short-lived bridge if you genuinely can't touch the code yet — never as the endpoint.
2. **The right move is an object-storage adapter.** Flysystem is already there in Laravel; set the disk to `s3` and route every read/write through `Storage::disk()` instead of a raw path. Your business logic shouldn't know "where the file is"; hide it behind the abstraction the framework gives you. This is boringly proven, and that's exactly why it's right.
3. **Don't assume a shared mount for image manipulation.** Pull the file to a `tmp` file and process it, or do it with a worker + presigned URLs. Break the "the disk is always here" assumption; take input/output from object storage and write the result back.
4. **Keep legacy churn minimal.** Hide all file access behind the framework's filesystem abstraction; flip it with a single disk config rather than migrating each call site to `s3` by hand. The smaller the surface you touch, the lower the risk.
5. **Migrate with dual-read.** Run for a while on "**read S3 first, fall back to local**" while you backfill existing images in the background. Once everything's moved, flip reads fully to S3 and decommission local disk.

**Bottom line:** don't spend money and latency on EFS. I'd go straight to a Flysystem `s3` disk, read/write everything through `Storage::disk()`, and solve manipulation with a temp file/worker. Migrate with dual-read for zero downtime, and once the backfill is done, remove local disk. Boring, but the kind of architecture you set up once and forget.

## Related Reading

- [Why I Prefer Boring Architecture](https://sade.dev/en/journal/why-boring-architecture) — sade.dev
- [Graceful degradation: how do I isolate non-critical services under load?](https://www.muhammetsafak.com.tr/en/just-ask/graceful-degradation-isolating-non-critical-services-under-load/) — Just Ask
- [Multi-tenant SaaS database isolation: separate DB, schema, or tenant_id?](https://www.muhammetsafak.com.tr/en/just-ask/multi-tenant-saas-database-isolation-for-10k-tenants/) — Just Ask
- [Should I put my API server behind a Cloudflare Tunnel and close port 443 to the internet?](https://www.muhammetsafak.com.tr/en/just-ask/should-i-put-my-api-behind-a-cloudflare-tunnel/) — Just Ask
