Heroku Dyno Ephemeral Filesystem: Stateless Design Requirements and Data Boundaries
Heroku dynos have an ephemeral filesystem that is lost on restart and cycling. Design for stateless apps with external storage, config vars, and managed data services, and verify behavior with dyno restarts and scaling checks.
09 Oct 2025, 12:57 UTC

The problem is local state disappearing, not disk space
On Heroku, a dyno is a single isolated container that runs your app. Each dyno has an ephemeral filesystem. Writes to /tmp or any local path are lost when the dyno restarts or is replaced. Dynos are cycled at least once every 24 hours and are also replaced on deploys, scaling events, and platform maintenance. The useful takeaway is to treat every dyno as read-only and short-lived, and move all durable state outside the dyno.
Requirements for a Heroku-deployable service
Stateless process. The app must produce the same response for the same request regardless of which dyno handles it. Configuration must come from environment variables, not files baked into the slug.
No local persistence. User uploads, generated files, logs, and session data cannot live on the dyno disk. The filesystem is backed by RAM in practice, so large writes can exhaust memory and trigger R14 errors.
Shared-nothing scaling. Traffic is distributed by Heroku's routing mesh to any healthy dyno. There is no shared filesystem between dynos, so a file written by Dyno A is invisible to Dyno B.
Smallest suitable design
Keep the app code stateless and externalize state to managed services.
Configuration via config vars. Set secrets and toggles with the Heroku CLI where the app runs. Run from a terminal with Heroku CLI authenticated and collaborator access to the app.
heroku config:set APP_ENV=production --app <app-name>Risk: config vars are visible to anyone with app access. Do not store large blobs or certificates there.
Persistent data via add-ons. Use Heroku Postgres for relational data, and an object store such as AWS S3 for uploads. Write uploads directly to S3 from the app or via a signed URL, and store only the object key in the database.
Session and cache via external store. Store sessions in Redis or Postgres, not in local memory or files. A distributed cache prevents session loss when a dyno cycles or when you scale from 1 to N dynos.
Logging to stdout. Write logs to stdout/stderr so Heroku Logplex can capture them. Do not write log files to disk.
Trust and data boundaries
Dyno boundary: untrusted and replaceable. Treat the dyno as a compute unit you do not control. Do not trust data that remains on disk across requests.
Platform boundary: the routing mesh is outside your app. It may route successive requests from the same user to different dynos. Any affinity must be implemented in cookies or tokens, not in local state.
Data boundary: durable data lives in external services with their own access controls and backups. The app only holds references, e.g., S3 keys or database IDs.
Operational checks
Verify ephemeral behavior. Deploy a test app that writes a file to ./data on start. Restart the dyno via CLI and check that the file is gone on the next request. This demonstrates the loss of local state.
heroku dyno restart web --app <app-name>Required permission: app collaborator. Risk: restart causes brief downtime on a single dyno.
Verify cycling. Check the Heroku Dashboard Dyno metrics for restart history to observe routine cycles.
Verify shared-nothing. Scale to two web dynos and write a request-specific marker to a local file. Subsequent requests will hit different dynos and the marker will not be visible everywhere, confirming no shared filesystem.
Failure modes
Dyno restart data loss. Any file written locally is deleted. Mitigation: never rely on local writes for durability.
Memory pressure from disk writes. Large temporary files consume RAM. Mitigation: stream uploads to S3 and use /tmp only for small short-lived processing.
Session loss on scale. Sessions stored in memory disappear when a dyno is removed. Mitigation: external session store.
Stale slug files. Files created at build time are immutable at runtime. Attempts to modify them fail.
Conditions that change the design
If you need low-latency local caching, add a managed cache like Redis and set an explicit eviction policy. Do not use the dyno filesystem as a cache.
If you require file processing, move processing to a worker dyno and write results to object storage, then notify the web dyno via the database or a queue.
If compliance requires data residency, select add-ons in the required region and avoid writing data to ephemeral storage that could be replicated.
Limitations: this note assumes standard Heroku dynos and the 12-factor methodology. Private Spaces or custom buildpacks do not change the ephemeral nature of the filesystem.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.