Skip to content
Local Drive

Environment variables

Every setting, what it does, and which ones are runtime editable instead.

Every value is read once at process start and validated with a clear error naming whatever is missing or malformed.

You do not have to set any of this. docker compose up -d works on a fresh clone with no .env at all, and localdrive setup or localdrive init writes a complete one for you. This page is for when you want to change something.

A commented file listing every variable with its default ships in the repository at server/.env.example. Copy it to .env and edit, or read it alongside this page.

Two that are read before .env is

These are looked up in the process environment only. Putting them in .env cannot work, because they are what the server uses to decide where .env is.

Variable Default What it does
LOCALDRIVE_HOME The platform's own data directory The install folder. Everything else, including .env and data/, is found relative to this.
LD_SECRETS_DIR Beside the database Where generated secrets are written. Worth changing only if the database lives somewhere you would rather not keep keys.

LOCALDRIVE_HOME is the one to reach for when running as a system service, where the account has no home directory of its own. See Keeping it running.

A key written twice

Changing a setting by appending a line is the obvious thing to do:

echo 'LD_PORT=80' >> .env

It leaves the file with LD_PORT in it twice, and the first one wins. The server takes the first value it sees, and so does localdrive status, so the two agree with each other, but neither uses the line you just added.

Edit the existing line rather than appending a second one. A real environment variable still beats the file either way.

Secrets are generated, not typed

JWT_SECRET, MOUNT_HELPER_SHARED_SECRET, and LAN_DISCOVERY_SHARED_SECRET are created on first start if they are not already set, and written to data/db/secrets.env with owner only permissions. Each mixes fresh randomness with an identifier unique to that machine, so two installs never share one.

They are written down rather than regenerated each start, because a signing key that changed on every restart would invalidate every token in existence.

Setting any of them in the environment overrides the generated one. Deleting the file regenerates them, which signs everyone out.

How it is reached

Variable Default Notes
LD_PORT 7443 The one port everything serves on
LD_DOMAIN empty Leave empty unless you own one. Setting it turns on HTTPS
LD_TLS_EMAIL empty Renewal notices, only with a domain
LD_TLS auto off stops the server asking for its own certificate. Docker pins this, since Caddy holds it there
PUBLIC_BASE_URL empty Absolute base for share and invite links
CORS_ALLOWED_ORIGINS empty Empty answers any origin. Set it to a comma separated list to narrow that. See below

Which pages may talk to your server

CORS_ALLOWED_ORIGINS is empty by default, and empty means any origin may call the API. That is deliberate, and it is worth understanding before you decide whether to change it.

Everyone runs their own server. The page talking to it is one somebody else hosts, on a domain this server has never heard of, so a list of permitted origins is not something it can know in advance. Refusing by default would mean every browser client fails until its address is typed into a config file.

What that does and does not mean:

  • It does not make your files public. Every request still needs a token, and the browser still refuses to send credentials it does not have. A page that cannot sign in cannot read anything.
  • It does mean a page on any domain can attempt a call. It gets an unauthorised answer without a token.
  • Media is readable cross-origin too. Thumbnails and video are fetched by the element itself, and a stricter policy makes the browser drop the response, which shows up as every picture being broken.

Set it when you know exactly which client you use and want the smaller surface:

CORS_ALLOWED_ORIGINS=https://drive.example.com,https://files.example.com

Exact origins, comma separated, scheme included. Once it is set, anything not listed is refused, including the web client if you forget to add it.

The websocket follows the same rule, so a client that may call the API may also open a socket to it.

Process

Variable Default
APP_ENV production
LOG_LEVEL info
LISTEN_ADDR :8080

Storage

Variable Default
DB_PATH /data/db/localdrive.sqlite
LIBRARY_PATH /data/library
EXTERNAL_MOUNTS_PATH /data/external
DATA_DIR ./data (host side)

Helpers

Variable Notes
MOUNT_HELPER_SOCKET Empty disables drive management
MOUNT_HELPER_SHARED_SECRET At least 16 characters when the socket is set
LAN_DISCOVERY_SOCKET Empty disables network announcing
LAN_DISCOVERY_SHARED_SECRET At least 16 characters when the socket is set

Authentication

Variable Default Notes
JWT_SECRET none Required, at least 32 characters, rejected if it looks like a placeholder
ACCESS_TOKEN_TTL 15m
REFRESH_TOKEN_TTL 30d Accepts a d suffix
ARGON2_MEMORY_KIB 65536 64 MB per hash in flight; raise if you have RAM
ARGON2_TIME 2
ARGON2_THREADS 2

Limits

Variable Default
MAX_UPLOAD_CONCURRENCY 4
WORKER_POOL_SIZE 2
MAX_READ_CONNS 4
DEFAULT_QUOTA_BYTES 0, meaning unlimited
TRASH_RETENTION_DAYS 30
VERSION_RETENTION_COUNT 20
VERSION_RETENTION_DAYS 180

Seeds, not settings

These three only apply the first time the server starts. After that they live in the database and are changed in Settings, Server, because deciding later to open or close signups should be a normal setting change rather than a redeploy.

Variable Default
ENABLE_LAN_DISCOVERY_DEFAULT true
REQUIRE_DEVICE_APPROVAL_DEFAULT true
ALLOW_SELF_REGISTRATION_DEFAULT false

Optional

Variable Notes
ENCRYPTION_KEY Turns on per file encryption at rest; incompatible with the browsable mirror
METRICS_ENABLED Exposes /metrics in Prometheus text format
LOCALDRIVE_FETCH_FFMPEG Set to false to stop the server fetching ffmpeg by itself. See Requirements