# Persistent Storage

> Where to put user uploads and other files that must survive deploys.

Every deploy on Potions unpacks a fresh release into a clean slot directory (blue/green) and then switches traffic. Because of this, anything written to the release directory is ephemeral (including `priv/static/uploads/`) and doesn't survive between deploys.

To solve this, Potions auto-provisions a per-app **persistent storage directory**.

## The Basics

Every app gets a directory at `/opt/potions/<app_name>/storage` on the server. It's exposed two ways:

- The `STORAGE_DIR` environment variable (auto-generated and visible on your **Environment** tab)
- A `./storage` symlink inside each release slot, so relative paths from your app's working directory also work

Files written here:

- ✅ **Survive deploys** - the directory lives next to (not inside) the release slots
- ✅ **Survive rollbacks** - same physical directory, regardless of which slot is active
- ❌ **Are deleted when the app is deleted** - `rm -rf /opt/potions/<app>` removes everything

## Writing Files

Read the path from `STORAGE_DIR` and use it like any other directory. For local development, where `STORAGE_DIR` isn't set, you can add a fallback in `config/runtime.exs`:

```elixir
# config/runtime.exs
storage_dir = System.get_env("STORAGE_DIR") || Path.expand("../storage", __DIR__)
File.mkdir_p!(storage_dir)
config :my_app, :storage_dir, storage_dir
```

Don't forget to add `/storage` to your `.gitignore`. You can use the directory however fits your app - subdirectories like `images/`, `videos/`, or per-user namespaces all work. Subdirectories you create at runtime inherit the same `deploy:deploy` ownership as the storage root.

## Serving Files Publicly

Because files in `STORAGE_DIR` are outside `priv/static/`, Phoenix's default static handler doesn't see them. To serve them you'll need to add a separate `Plug.Static` entry to your endpoint:

```elixir
# lib/my_app_web/endpoint.ex
plug :serve_uploads

defp serve_uploads(conn, _opts) do
  Plug.Static.call(
    conn,
    Plug.Static.init(
      at: "/uploads",
      from: Application.fetch_env!(:my_app, :storage_dir),
      gzip: false
    )
  )
end
```

> Why the wrapper? `plug Plug.Static, ...` reads its options at compile time, but `STORAGE_DIR` is set at runtime. The function plug defers the lookup.

## Things to Know

- **No automatic cleanup.** Files here aren't automatically cleaned up, so disk usage grows over time. If you anticipate storing many files here, consider cloud storage. 
- **The path is reserved.** You can't manually add or override a `STORAGE_DIR` env var. This prevents accidental misconfiguration.
