-
Notifications
You must be signed in to change notification settings - Fork 3
Configuration
| Variable | Required | Description | Default | Example |
|---|---|---|---|---|
APP_PORT |
No | Port to expose on host | 4321 |
8080 |
PUID |
No | User ID for file permissions (Linux) | 1000 |
1000 |
PGID |
No | Group ID for file permissions (Linux) | 1000 |
1000 |
TZ |
No | Timezone for notification schedules | UTC |
Europe/Amsterdam |
POSTGRES_USER |
Yes | PostgreSQL username | vitransfer |
vitransfer |
POSTGRES_PASSWORD |
Yes | PostgreSQL password (hex only) | - | Generated with openssl rand -hex 32
|
POSTGRES_DB |
Yes | PostgreSQL database name | vitransfer |
vitransfer |
REDIS_PASSWORD |
Yes | Redis password (hex only) | - | Generated with openssl rand -hex 32
|
REDIS_DB |
No | Redis logical database index (lets you share one Redis instance across apps) | 0 |
3 |
ENCRYPTION_KEY |
Yes | Data encryption key (base64) | - | Generated with openssl rand -base64 32
|
JWT_SECRET |
Yes | JWT signing secret (base64) | - | Generated with openssl rand -base64 64
|
JWT_REFRESH_SECRET |
Yes | JWT refresh secret (base64) | - | Generated with openssl rand -base64 64
|
ADMIN_EMAIL |
Yes | Initial admin email | - | admin@example.com |
ADMIN_PASSWORD |
Yes | Initial admin password | - | Admin1234 |
ADMIN_NAME |
No | Initial admin display name | Admin |
Jane Doe |
SHARE_TOKEN_SECRET |
Yes | Secret for signing share tokens and recipient-portal session tokens (both JWTs are HS256-signed; the type claim discriminates between them). |
none | |
HTTPS_ENABLED |
No | Enable HTTPS enforcement (HSTS) | true |
false for localhost |
CPU_THREADS |
No | Override CPU thread count used by the worker/FFmpeg | auto-detect | 8 |
WORKER_CONCURRENCY |
No | Override concurrent video jobs (bypasses computed allocation) | computed | 4 |
FFMPEG_THREADS_PER_JOB |
No | Override FFmpeg threads per transcode (bypasses computed allocation) | computed | 16 |
FFMPEG_PRESET |
No | Override FFmpeg encoding preset (ultrafast–veryslow) |
faster |
medium |
DEBUG_WORKER |
No | Enable verbose worker logging | false |
true |
DEBUG_EXTERNAL_NOTIFICATIONS |
No | Enable verbose external notification logging | false |
true |
STORAGE_PROVIDER |
No | Storage backend: local or s3
|
local |
s3 |
S3_ENDPOINT |
When STORAGE_PROVIDER=s3
|
S3-compatible endpoint URL | — | https://s3.amazonaws.com |
S3_BUCKET |
When STORAGE_PROVIDER=s3
|
Bucket name (must already exist) | — | vitransfer |
S3_REGION |
When STORAGE_PROVIDER=s3
|
AWS region or any value for region-agnostic stores | us-east-1 |
us-east-1 |
S3_ACCESS_KEY_ID |
When STORAGE_PROVIDER=s3
|
Access key ID | — | |
S3_SECRET_ACCESS_KEY |
When STORAGE_PROVIDER=s3
|
Secret access key | — | |
S3_SERVER_MULTIPART_CONCURRENCY |
No | Concurrent multipart parts when the worker uploads transcoded outputs to S3. Range 1–16. | 4 |
8 |
TRANSFER_STREAM_HWM_MB |
No | Per-transfer Node read buffer for FS streaming (downloads + video player). Memory per active transfer is roughly this value. Range 1–64 MiB. | 16 |
32 |
TRANSFER_STREAM_CHUNK_MB |
No | Cap on a single video-player Range request. Smaller = snappier seeks, larger = fewer round-trips. Does not apply to direct downloads. Range 1–64 MiB. | 4 |
8 |
- Use
openssl rand -hex 32for database passwords (URL-safe). - Use
openssl rand -base64 32/64for encryption keys and JWT secrets. - Avoid special characters in
ADMIN_PASSWORDdue to JSON parsing. -
HTTPS_ENABLEDalways overrides the admin setting. - Set
TZfor correct notification scheduling and due date reminder timing.
Set STORAGE_PROVIDER=s3 to have uploads and downloads bypass Node.js and go directly between the browser and your object store. No rebuild needed — the setting is read at runtime.
Tested with: MinIO AIStor (self-hosted Docker container). Other S3-compatible stores (AWS S3, Cloudflare R2, Backblaze B2, Garage, etc.) should work but have not been tested — please open an issue if you run into any problems.
How it works:
- Uploads — the browser requests presigned part URLs from ViTransfer, then PUTs each chunk straight to the store. Node.js never touches the file bytes. This applies to all uploads: videos, comment attachments, and client file submissions (reverse share).
- Individual downloads — the server generates a short-lived presigned GET URL and issues a 302 redirect. Node.js proxies nothing. This covers single video downloads, asset downloads, and client upload downloads.
- ZIP downloads — "Download All Videos" and single-video-with-assets ZIPs stream file data from S3 through the server, since the archive must be assembled before delivery. The ZIP is streamed to the browser as it is built (not buffered in memory).
- Worker — FFmpeg jobs stream directly from/to object storage via the SDK; no shared volume is needed.
Important: Local and S3 storage cannot be mixed. Switching from one to the other does not move or delete any files — they remain where they are — but ViTransfer will only read from the active backend. Projects stored in the previous backend will not work until their files are manually migrated to the new storage. There is no built-in migration tool. This is by design.
Important: The S3 variables are set in your .env file and are automatically passed to both the app and worker services via docker-compose.yml. Do not add them to individual services manually — both containers need the same S3 configuration.
Requirements:
- Create the bucket before starting ViTransfer.
- Configure CORS on the bucket to allow
GETandPUTrequests from your app origin (required for presigned uploads and downloads from the browser). - Set
STORAGE_PROVIDER=s3and theS3_*variables in your.env.
Example .env:
STORAGE_PROVIDER=s3
S3_ENDPOINT=https://s3.amazonaws.com # or your store's endpoint
S3_BUCKET=vitransfer
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
MinIO AIStor — uses path-style addressing, which ViTransfer enables automatically when S3_ENDPOINT is set. Set S3_ENDPOINT to your MinIO hostname (e.g. http://minio:9000).
AWS S3 — set S3_ENDPOINT=https://s3.amazonaws.com and the correct S3_REGION.
Cloudflare R2 — endpoint is https://<account-id>.r2.cloudflarestorage.com. Set S3_REGION=auto.
Backblaze B2 — endpoint is https://s3.<region>.backblazeb2.com. Use your B2 application key ID and key.
The browser needs direct access to your object store for two operations:
-
PUT— uploading file parts via presigned URLs -
GET— video playback and file downloads via presigned URLs
The ETag response header must also be exposed (browsers block it by default), or uploads will fail with "Part returned no ETag".
All other S3 operations (multipart initiation, completion, abort) happen server-side and do not require CORS.
MinIO AIStor — CORS is typically permissive by default, but if you've locked it down:
mc alias set myminio http://minio:9000 <access-key> <secret-key>
mc admin config set myminio/ api cors_allow_origin=https://vitransfer.example.com
mc admin service restart myminio/AWS S3 / R2 / B2 — configure the bucket CORS policy via the provider's console or CLI.
Example JSON CORS policy:
[
{
"AllowedOrigins": ["https://vitransfer.example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]The worker computes its CPU allocation from a budget of half the available threads, so the worst case (all video workers plus the clean preview worker encoding at once) never exceeds ~50% of the host and the server stays responsive during processing. CPU_THREADS overrides the auto-detected thread count that the budget is based on.
- Worker concurrency — 2 concurrent video jobs on hosts with 24+ threads, otherwise 1
-
Threads per job — the budget split across video workers and the clean preview worker (the
-threadsflag), capped at 8 -
Encoding preset — always
faster, the speed/size sweet spot for CRF-based review previews
| Threads | Concurrent jobs | FFmpeg threads/job | Max threads used |
|---|---|---|---|
| 2 | 1 | 1 | 2 (~100%) |
| 4 | 1 | 1 | 2 (~50%) |
| 8 | 1 | 2 | 4 (~50%) |
| 16 | 1 | 4 | 8 (~50%) |
| 24 | 2 | 4 | 12 (~50%) |
| 32 | 2 | 5 | 15 (~47%) |
| 64 | 2 | 8 | 24 (~38%) |
"Max threads used" includes both the video processing worker and the clean preview worker (generates non-watermarked versions on approval). The resolved allocation is printed at worker startup ([CPU CONFIG] log lines).
For hosts where the 50% budget is not what you want (dedicated transcode machines, high-core servers), three variables bypass the computed values:
-
WORKER_CONCURRENCY— concurrent video jobs (1-16) -
FFMPEG_THREADS_PER_JOB— FFmpeg threads per transcode (1-64) -
FFMPEG_PRESET— any x264 preset fromultrafasttoveryslow(e.g.mediumfor smaller files at slower encode speed)
Overrides are taken as-is: setting them high deliberately opts out of the 50% headroom guarantee.
-
Docker: Containers may report the host CPU count rather than the cgroup limit. Set
CPU_THREADSto match your--cpusordeploy.resources.limits.cpusvalue for accurate allocation. -
Shared servers: Lower
CPU_THREADSto leave headroom for other services. - Dedicated servers: Leave unset — auto-detection works correctly.
An 8-thread server with CPU_THREADS unset (auto-detected as 8):
Detected threads: 8
→ 1 concurrent video job
→ 2 FFmpeg threads per job
→ Preset: faster
→ Max 4/8 threads in use (~50%)
→ Remaining threads available for the web app, database, and uploads
Three optional environment variables control upload/download throughput and memory use. The defaults are tuned for a modern dedicated server and don't need adjustment in most setups. Tweak only if you're chasing throughput on a fast LAN, or trimming memory on a small VPS.
Per-transfer read buffer used when streaming files from local storage (downloads and the video player). Memory consumed per active transfer is roughly this value × the number of concurrent transfers.
-
Default
16is fine for any server with ≥ 4 GB RAM. -
Raise to
32or64on a beefy server (e.g. ≥ 16 GB RAM, fast disk) for slightly higher per-transfer throughput. -
Lower to
4or8if you're memory-constrained and/or expect many concurrent downloads. - Range: 1–64 MiB.
Cap on a single video-player Range request. The HTML5 player issues many small Range requests as the user seeks; this cap keeps the UI responsive on seek and prevents a single tab from hogging server I/O. Does not apply to direct downloads — those serve the full file.
-
Default
4matches typical browser HTML5-video range behavior. -
Raise to
8or16if your players are doing long sequential reads (e.g. preview-only streaming with no seeking). - Range: 1–64 MiB.
Number of concurrent multipart parts the worker uploads in parallel when shipping transcoded outputs back to your S3 store. Only relevant in S3 mode.
-
Default
4is conservative and works well on most networks. -
Raise to
8or12on a fast LAN to MinIO/Ceph for noticeably faster post-transcode uploads. -
Lower to
2if your S3 endpoint is rate-limited or shared with many tenants. - Range: 1–16.
| Server profile | TRANSFER_STREAM_HWM_MB |
S3_SERVER_MULTIPART_CONCURRENCY |
|---|---|---|
| Small VPS (2 GB RAM) | 4 |
2 |
| Default (4–16 GB RAM) |
16 (default) |
4 (default) |
| Dedicated server (≥ 32 GB RAM, fast LAN) | 32 |
8–12
|
| Cloud (S3 over the public internet) |
16 (default) |
4–8
|
Navigation: Home | Features | Installation | Platform Guides | Configuration | Admin Settings | Usage Guide | Client Guide | Security | Maintenance | Troubleshooting | Screenshots | Contributing | License