Storage UI reads all server-side settings from environment variables. Storage credentials stay on the server and are never shipped to the browser.
For local development, copy the example file and edit it:
cp .env.example .env.localOn a hosting platform (Vercel, Docker, etc.), set the same variables in that platform's environment settings.
Application
| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_APP_URL | localhost:3000 | Public URL of the app. |
NEXT_PUBLIC_BASE_PATH | (root) | Serve from a subpath, e.g. /drive. Also redirects / to that path. |
NEXT_PUBLIC_ASSET_PREFIX | (none) | Set only when static assets are served from a separate origin. |
Authentication
Set a username and password to turn on the built-in login screen. Leave either blank to keep the app open.
AUTH_USERNAME=admin
AUTH_PASSWORD=change-me
AUTH_SECRET=a-long-random-string
| Variable | Required | Description |
|---|---|---|
AUTH_USERNAME | For login | Login username. |
AUTH_PASSWORD | For login | Login password. |
AUTH_SECRET | Recommended | Signs the session cookie. Without it, the password doubles as the signing key, so changing the password logs everyone out. |
Sessions last 7 days. The login form can also carry a bot challenge, see Turnstile below.
Storage connections
Each connection is a numbered group of variables, STORAGE_1_* through STORAGE_50_*. Users can still add more connections from the UI at runtime. Which variables a slot uses depends on its provider. S3 and the services compatible with it share one set, WebDAV takes another.
S3 and compatible services
A connection loads once its BUCKET, ACCESS_KEY_ID, and SECRET_ACCESS_KEY are set (plus REGION or ENDPOINT for some providers, noted below).
| Variable | Required | Description |
|---|---|---|
STORAGE_n_BUCKET | Yes | Bucket name. |
STORAGE_n_ACCESS_KEY_ID | Yes | Access key ID (Tencent SecretId, B2 key ID). |
STORAGE_n_SECRET_ACCESS_KEY | Yes | Secret access key (Tencent SecretKey, B2 application key). |
STORAGE_n_PROVIDER | No (s3) | Provider, see list below. |
STORAGE_n_NAME | No | Display name in the sidebar. Defaults to the bucket name. |
STORAGE_n_REGION | Provider | Required for alibaba, tencent, backblaze-b2. Ignored by r2. Defaults to auto for S3. |
STORAGE_n_ENDPOINT | Provider | Required for minio. Optional override for other S3-compatible providers. |
STORAGE_n_ACCOUNT_ID | R2 only | Cloudflare account ID (R2 derives its endpoint from this). |
STORAGE_n_FORCE_PATH_STYLE | No | Path-style addressing. Defaults to true for minio, false otherwise. |
STORAGE_n_PUBLIC_BASE_URL | No | Public or CDN base URL for file links. |
STORAGE_n_READ_ONLY | No | Set to true to disable all edits for this bucket. |
Supported providers: s3, r2, alibaba, tencent, backblaze-b2, minio, s3-compatible.
Always set a real region.
STORAGE_1_PROVIDER=s3
STORAGE_1_NAME=Production
STORAGE_1_BUCKET=my-bucket
STORAGE_1_REGION=us-east-1
STORAGE_1_ACCESS_KEY_ID=...
STORAGE_1_SECRET_ACCESS_KEY=...
WebDAV
Works with Nextcloud, ownCloud, Apache mod_dav, and most NAS boxes. There is no bucket, and ENDPOINT is the only required variable.
STORAGE_8_PROVIDER=webdav
STORAGE_8_NAME=Nextcloud
STORAGE_8_ENDPOINT=https://cloud.example.com/remote.php/dav/files/alice
STORAGE_8_USERNAME=alice
STORAGE_8_PASSWORD=...
STORAGE_8_ROOT=/Photos
| Variable | Required | Description |
|---|---|---|
STORAGE_n_PROVIDER | Yes | Must be webdav. |
STORAGE_n_ENDPOINT | Yes | The WebDAV collection URL. |
STORAGE_n_USERNAME | No | Username. Falls back to ACCESS_KEY_ID. |
STORAGE_n_PASSWORD | No | Password. Falls back to SECRET_ACCESS_KEY. |
STORAGE_n_AUTH_TYPE | No | Auth strategy, see below. |
STORAGE_n_ROOT | No | Browse only this folder on the server. Defaults to the collection root. |
STORAGE_n_NAME | No | Display name in the sidebar. Defaults to the server URL. |
STORAGE_n_PUBLIC_BASE_URL | No | Public base URL for file links, see below. |
STORAGE_n_READ_ONLY | No | Set to true to disable all edits for this connection. |
AUTH_TYPE accepts password (the default when a username is set), basic (the same strategy under another name), digest, auto (try anonymous, fall back to the challenge the server returns), and none. An unrecognized value is ignored with a warning.
Two differences from the S3 providers.
- WebDAV always routes through this app's server, whatever the Direct client requests setting says, so the server needs network access to the WebDAV host. On a private address, see Private endpoints below.
- Without
PUBLIC_BASE_URL, file bytes stream through the app server rather than through a signed URL. Set it to a public HTTP origin fronting the same files to get direct preview URLs.
Private endpoints
| Variable | Default | Description |
|---|---|---|
ALLOW_PRIVATE_ENDPOINTS | false | Let connections added from the UI point at a private address. |
In production, a connection a user adds from the UI may not point at a private address, because the server fetches that endpoint on the user's behalf. Blocked are localhost, *.local, *.internal, the loopback, link-local, and carrier-grade NAT ranges, and the private ranges 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16.
ALLOW_PRIVATE_ENDPOINTS=true
Set it when the deployment legitimately browses a LAN NAS or a sibling container. Two things it does not cover.
STORAGE_n_*connections are the operator's own and are never checked, so a self-hosted setup that only uses env-configured buckets does not need this.- Local development is exempt, so the restriction appears only once the app runs with
NODE_ENV=production.
Turnstile
Add a Cloudflare Turnstile challenge to the login form. Create a widget in the Cloudflare dashboard, then set both keys.
TURNSTILE_SITE_KEY=0x4AAAAAAA...
TURNSTILE_SECRET_KEY=0x4AAAAAAA...
| Variable | Required | Description |
|---|---|---|
TURNSTILE_SITE_KEY | For the challenge | Widget site key. Public, sent to the browser. |
TURNSTILE_SECRET_KEY | For the challenge | Widget secret key. Server-side only. |
The challenge stays off unless both are set, and a warning is logged when only one is. It applies to the login form, so it does nothing without AUTH_USERNAME and AUTH_PASSWORD.
Neither key is read at build time, so the same Docker image works with any pair of keys, and both can be changed with a restart.