Configuration
Environment variables and persistent storage
Vaze is configured entirely through environment variables and a single mounted volume.
If you used the quick install, all of this
lives in the .env next to your compose.prod.yaml (./vaze/.env by default).
Edit it and apply with docker compose -f compose.prod.yaml up -d.
Environment variables
| Variable | Required | Description |
|---|---|---|
BASE_URL | Yes | The public base URL of your Vaze instance, e.g. https://files.example.com. Used by authentication and for building absolute URLs. |
AUTH_SECRET | Yes | A long random string used to sign session tokens. Keep it secret and stable — rotating it invalidates existing sessions. |
VAZE_PORT | No (3000) | Host port the container is published on. Compose-only — it maps to port 3000 inside the container. |
VAZE_BIND_ADDR | No (0.0.0.0) | Host address the port is bound to. Set 127.0.0.1 when a reverse proxy fronts Vaze, so the container isn't reachable directly. |
VAZE_VERSION | No (latest) | Image tag to run. Pin it (e.g. 2.2.0) if you want upgrades to be a deliberate step. |
TZ | No (UTC) | Timezone used inside the container. |
PORT | No (3000) | The port the server listens on inside the container. |
Limits
| Variable | Default | Description |
|---|---|---|
MAX_UPLOAD_SIZE | 5gb | Largest single file accepted by an upload. Accepts b/kb/mb/gb/tb suffixes, e.g. 500mb. |
MAX_FILES_PER_REQUEST | 100 | Cap on the number of files in one multipart request. |
API_REQUEST_RETENTION_DAYS | 90 | How long API request-log rows are kept before being pruned. |
ACTIVITY_RETENTION_DAYS | 90 | How long activity history is kept before being pruned. |
MAX_DELETE_BATCH | 500 | Cap on the number of file ids accepted by one batch delete. |
MAX_MOVE_BATCH | 500 | Cap on the number of file ids accepted by one batch move. |
MAX_DOWNLOAD_BATCH | 1000 | Cap on the number of file ids accepted by one multi-file zip download. |
Uploads stream to disk, so MAX_UPLOAD_SIZE bounds what a single request may
store — not how much memory the server uses.
Hosting traffic for the dashboard is stored as one small row per day rather than per request, so it has no setting: counts per file are kept for 30 days and instance totals for a year. See Stats.
MAX_UPLOAD_SIZE, API_REQUEST_RETENTION_DAYS and ACTIVITY_RETENTION_DAYS
can be changed at runtime under Settings in the dashboard or the mobile
app, or with PUT /api/settings. Each variable
only sets the starting value: it is copied in the first time Vaze starts (or
first starts after an upgrade that adds the setting), and changing it later
has no effect.
File access
| Variable | Default | Description |
|---|---|---|
DEFAULT_FILE_VISIBILITY | public | Visibility given to an upload that doesn't ask for one. Set to private to make the instance private-first. |
DEFAULT_PRESIGN_TTL_SECONDS | 3600 | Lifetime of a signed URL when the caller doesn't specify one. |
MAX_PRESIGN_TTL_SECONDS | 604800 | Longest lifetime a signed URL may be minted with. Seven days, matching S3's cap. |
HOSTING_CACHE_MAX_AGE | 0 | max-age for public hosted responses. Zero revalidates every time, which is cheap because a match answers with a bodiless 304. Raise it when keys are effectively immutable. |
All four can be changed at runtime under Settings in the dashboard or the
mobile app, or with PUT /api/settings. Each
variable only sets the starting value: it is copied in the first time Vaze
starts (or first starts after an upgrade that adds the setting), and changing
it later has no effect.
Public files are served to anyone with the link; private files require a signed URL, an API key, or a dashboard session. See signing a URL.
Generate a strong secret with:
openssl rand -base64 32AUTH_SECRET is the one value worth backing up. The installer generates it
once and never overwrites it, but if you rebuild the .env by hand with a new
secret, every session is invalidated and users have to log in again — and
every outstanding signed URL stops working, since signing keys are derived
from it.
Persistent storage
Inside the container, Vaze keeps everything under /var/lib/vaze:
db/: the SQLite database with file metadata, users, and API keys.uploads/: all uploaded files and folders, stored as plain files on disk.tmp/: in-flight uploads, staged here and moved intouploads/once fully received. Cleared on startup.thumbnails/: cached image previews for the dashboard, rebuilt on demand.
The container runs as an unprivileged user, so a bind mount at /var/lib/vaze
must be writable by uid 1000.
compose.prod.yaml mounts a named Docker volume there:
volumes:
- vaze_data:/var/lib/vazeNamed volumes are the safer default — SQLite's locking is unreliable on bind mounts backed by network filesystems. If you want the data at a specific path on the host, swap it for a bind mount:
volumes:
- /srv/vaze:/var/lib/vazeTo back up the named volume:
docker run --rm -v vaze_vaze_data:/data -v "$PWD:/backup" alpine \
tar czf /backup/vaze-backup.tar.gz -C /data .Upgrading never touches this volume. The installer and
docker compose -f compose.prod.yaml up -d both replace only the container.
Vaze refuses to start unless /var/lib/vaze is on a mounted volume, since
without one every file and account is lost when the container is replaced.
Set ALLOW_EPHEMERAL_DATA=true to start without one anyway, for example on a
throwaway test instance.
Health checks
The image exposes GET /api/health, and compose.prod.yaml uses it as the
container healthcheck. Check the current state with:
docker compose -f compose.prod.yaml psReverse proxy
If you put Vaze behind a reverse proxy (nginx, Caddy, Traefik, …), set
BASE_URL to the public URL served by the proxy — for example
https://files.example.com — so login and generated file URLs use the right
origin.
Also set VAZE_BIND_ADDR=127.0.0.1 so the container is only reachable through
the proxy instead of being exposed on every interface:
BASE_URL=https://files.example.com
VAZE_BIND_ADDR=127.0.0.1Remember to raise your proxy's request body size limit if you plan to upload
large files (e.g. client_max_body_size in nginx).
Vaze