Skip to main content

Configuration

Every knob OpenBucket exposes, in one place. The standalone app reads its config from the environment; the embedded module (OpenBucketModule.forRoot) takes the same settings as typed options. Both fail fast — a missing or malformed secret refuses to boot rather than starting insecure.

Grab the essentials and go:

# The five required standalone settings:
DATA_DIR=/data
JWT_SECRET=$(openssl rand -base64 48)
ADMIN_PASSWORD_HASH=$(node scripts/hash-password.mjs 'your-admin-password')
ROOT_ACCESS_KEY_ID=AKIAEXAMPLE000000000
ROOT_SECRET_ACCESS_KEY=$(openssl rand -base64 48)

Everything else has a sensible default. The full, commented template lives in .env.example.

Generate strong secrets

JWT_SECRET, ROOT_SECRET_ACCESS_KEY, KEY_ENCRYPTION_SECRET, WEBHOOK_SECRET, and a token-mode METRICS_TOKEN are all validated as strong secrets: at least 32 characters, at least 8 distinct characters, not a single repeated character, and not a known placeholder (changeme, secret, password, …). openssl rand -base64 48 clears the bar every time.

Standalone environment variables

The standalone app validates process.env on boot and refuses to start if anything required is missing or malformed. Unknown variables are ignored.

Core (required)

VariableRequiredDefaultNotes
DATA_DIRDirectory for the SQLite metadata DB, blob payloads, and the generated sse.key. No trailing slash.
JWT_SECRETStrong secret (≥ 32 chars). Signs admin JWTs.
ADMIN_PASSWORD_HASHargon2id hash — generate with node scripts/hash-password.mjs '<password>'.
ROOT_ACCESS_KEY_ID16–32 uppercase alphanumerics (^[A-Z0-9]{16,32}$).
ROOT_SECRET_ACCESS_KEYStrong secret (≥ 32 chars). The root SigV4 credential.

Runtime & admin auth

VariableDefaultNotes
NODE_ENVproductionOne of development / test / production.
PORT9000HTTP listen port.
MOUNT_PATH— (root)Route prefix everything mounts under — S3 (<MOUNT_PATH>/<bucket>/<key>), admin API (<MOUNT_PATH>/api/admin), admin console (<MOUNT_PATH>/admin), health & metrics. Empty = root. Set it to run behind a reverse proxy at a subpath (e.g. /storage). Normalized to a leading slash, no trailing slash.
LOG_LEVELinfotrace / debug / info / warn / error / fatal.
ADMIN_USERNAMEadminBootstrap admin login.
JWT_ACCESS_TTL_SECONDS900Access-token lifetime (60–3600).
JWT_REFRESH_TTL_SECONDS604800Refresh-token lifetime (3600–2592000, i.e. 1 h–30 d).
Running at a subpath

MOUNT_PATH moves the whole OpenBucket surface under one prefix so it can sit behind a reverse proxy at, say, https://example.com/storage/… without a dedicated subdomain. The admin SPA's <base href> is rewritten to match, and the admin API stays guarded at <MOUNT_PATH>/api/admin/*. Point your S3 client's endpoint at https://example.com/storage (path-style). It is the standalone twin of the embedded module's OpenBucketModule.forRoot({ mountPath }). See Deployment → behind a reverse proxy.

S3 protocol

VariableDefaultNotes
OPENBUCKET_REGIONus-east-1Region reported to clients. Match it in your SDK config.
OPENBUCKET_ENDPOINTOptional DNS-safe hostname clients use to reach the store.
OPENBUCKET_SSE_KEYgeneratedbase64 of exactly 32 bytes for at-rest SSE-S3. Auto-generated to <DATA_DIR>/sse.key on first boot when unset.
KEY_ENCRYPTION_SECRETroot secretStrong secret. KEK material for scoped sub-key secrets at rest. Falls back to ROOT_SECRET_ACCESS_KEY when unset — so rotating the root secret without setting this invalidates existing sub-key secrets.

Limits & quotas

VariableDefaultNotes
MAX_OBJECT_SIZE_MB5120Per-object size cap in MiB (5 GiB). Max 5242880.
MAX_MULTIPART_PARTS10000Max parts per multipart upload (also the ceiling).
MULTIPART_TTL_HOURS24Abandoned-multipart reaper TTL.
MAX_CONCURRENT_MULTIPART_UPLOADS1000Cap on in-flight multipart sessions. 0 = unlimited.
DATA_DIR_MIN_FREE_BYTES104857600Refuse writes below this free-space reserve (100 MiB).
STORAGE_QUOTA_BYTES0Aggregate stored-byte quota. 0 = disabled.
STORAGE_QUOTA_OBJECTS0Aggregate object-count quota. 0 = disabled.
S3_THROTTLE_LIMIT1000Per-IP S3 data-plane request budget per window. 0 disables.
S3_THROTTLE_TTL_MS60000S3 throttle window.

Image transforms

On-the-fly, cached image derivatives (?w=&h=&fit=&format=&q=) on GET.

VariableDefaultNotes
IMAGE_TRANSFORM_ENABLEDtrueMaster switch. false makes every GET serve the plain object.
MAX_TRANSFORM_DIMENSION4096Max requested output width/height in px. Ceiling 16384.
MAX_TRANSFORM_INPUT_BYTES52428800Refuse to transform a source larger than this (50 MiB).
IMAGE_TRANSFORM_LIMIT_INPUT_PIXELS576000000Decoded-canvas ceiling (decompression-bomb guard, 24000×24000).
IMAGE_TRANSFORM_CONCURRENCY4Max concurrent sharp operations. Max 64.
DERIVATIVE_CACHE_MAX_BYTES5368709120Derivative-cache size ceiling (5 GiB). 0 = unbounded.

Analytics & audit

VariableDefaultNotes
USAGE_ROLLUP_INTERVAL_MS900000Usage-rollup sample interval (floor 60000).
USAGE_RETENTION_DAYS90Prune usage samples older than this.
AUDIT_RETENTION_DAYS90Prune persisted audit rows older than this.
AUDIT_FLUSH_MS2000Audit buffer → SQLite flush interval (floor 250).
AUDIT_BUFFER_MAX10000Max buffered audit events before oldest is dropped (min 100).

Object-event webhooks

Setting WEBHOOK_URL enables durable, HMAC-signed delivery of object events. The URL must be https unless the host is loopback; the secret is then required.

VariableRequiredDefaultNotes
WEBHOOK_URLTarget endpoint. Presence enables the delivery outbox.
WEBHOOK_SECRETwhen URL setStrong secret. HMAC-SHA256 signing key.
WEBHOOK_EVENTSall threeComma-separated: object.created,object.deleted,multipart.completed.
WEBHOOK_MAX_ATTEMPTS8Attempts before dead-letter (1–50).
WEBHOOK_TIMEOUT_MS5000Per-request timeout (500–60000).
WEBHOOK_POLL_MS15000Delivery tick interval (1000–300000).

Async replication

Mirror every object mutation to an external S3-compatible target. When OB_REPLICATION_ENABLED=true, the bucket and both credentials are required together — a partial config refuses to boot.

VariableRequired*DefaultNotes
OB_REPLICATION_ENABLEDfalseMaster switch. Off ⇒ zero cost, outbox stays empty.
OB_REPLICATION_ENDPOINTS3-compatible endpoint (R2/B2/MinIO). Omit for real AWS S3. http:// warns at boot (plaintext).
OB_REPLICATION_REGIONus-east-1Target region.
OB_REPLICATION_BUCKETRemote target bucket (must already exist).
OB_REPLICATION_ACCESS_KEY_IDTarget credential.
OB_REPLICATION_SECRET_ACCESS_KEYTarget credential (never logged).
OB_REPLICATION_FORCE_PATH_STYLEtruetrue for MinIO/S3-compat; false for AWS.
OB_REPLICATION_MAX_ATTEMPTS12Dead-letter cap (1–50).
OB_REPLICATION_DRAIN_INTERVAL_MS5000Drain tick interval (1000–300000).
OB_REPLICATION_BATCH_KEYS50Distinct keys drained per tick (1–1000).
OB_REPLICATION_LARGE_OBJECT_THRESHOLD_BYTES67108864Stream via multipart above this (64 MiB).

* Required only when OB_REPLICATION_ENABLED=true.

Cold-object tiering

Offload cold objects to the replication target and rehydrate transparently on read. A no-op unless a replication target is also configured.

VariableDefaultNotes
OPENBUCKET_TIER_ENABLEDfalseMaster switch.
OPENBUCKET_TIER_INLINE_MAX_BYTES268435456Proxy read-through at/under this size; larger ⇒ presigned redirect (256 MiB).
OPENBUCKET_TIER_READTHROUGH_TIMEOUT_MS30000Latency bound on a proxied fetch before 503 SlowDown.
OPENBUCKET_TIER_MAX_CONCURRENT_REHYDRATE8Global concurrent-rehydration cap. 0 = unlimited.
OPENBUCKET_TIER_PRESIGN_TTL_SECONDS300TTL for presigned redirect URLs (30–3600).

Scheduled backups

Write a .zip snapshot on a schedule with union retention. When OB_SCHEDULED_BACKUP_ENABLED=true, exactly one of INTERVAL_MINUTES / CRON must be set.

VariableDefaultNotes
OB_SCHEDULED_BACKUP_ENABLEDfalseMaster switch.
OB_SCHEDULED_BACKUP_SCOPEinstanceinstance = one snapshot; buckets = one per bucket.
OB_SCHEDULED_BACKUP_INTERVAL_MINUTESFixed interval (5–43200). Mutually exclusive with CRON.
OB_SCHEDULED_BACKUP_CRON5-field cron. Mutually exclusive with INTERVAL_MINUTES.
OB_SCHEDULED_BACKUP_DIR<DATA_DIR>/backupsAbsolute snapshot directory.
OB_SCHEDULED_BACKUP_KEEP_LAST7Keep the newest N snapshots (hard floor, 1–1000).
OB_SCHEDULED_BACKUP_MAX_AGE_DAYS30Also keep anything younger than this (union, 1–3650).
OB_SCHEDULED_BACKUP_CHECK_INTERVAL_MS60000"Is a snapshot due?" wake tick (10000–3600000).
OB_SCHEDULED_BACKUP_PUSH_TO_REPLICATIONfalseAlso push each .zip to the replication target.

Integrity scrubbing

A background scrubber re-hashes local blobs against their stored SHA-256 to detect bit-rot. Strictly rate-limited so it never starves request traffic.

VariableDefaultNotes
OB_INTEGRITY_SCRUB_ENABLEDfalseMaster switch. A fresh install does zero extra I/O.
OB_INTEGRITY_SCRUB_INTERVAL_MS60000Tick interval (floor 1000).
OB_INTEGRITY_SCRUB_MAX_OBJECTS_PER_TICK1000Hard per-tick object cap (min 1).
OB_INTEGRITY_SCRUB_MAX_BYTES_PER_TICK1073741824Per-tick byte budget (1 GiB).

Restore caps & shutdown

VariableDefaultNotes
RESTORE_MAX_TOTAL_BYTES107374182400Total decompressed payload accepted from a restore archive (100 GiB).
RESTORE_MAX_ENTRY_BYTES5368709120Per-entry decompressed cap (5 GiB).
RESTORE_MAX_ENTRIES1000000Max payload entries in a restore archive.
RESTORE_MAX_MANIFEST_BYTES4194304Max buffered manifest.json bytes (4 MiB).
METRICS_MODEoffPrometheus scrape gate: off / public / token.
METRICS_TOKENStrong bearer token — required when METRICS_MODE=token.
OTEL_TRACING_ENABLEDfalseOpenTelemetry tracing (no-op unless @opentelemetry/api + an SDK are installed).
SHUTDOWN_DRAIN_MS30000In-flight drain budget on SIGTERM (1000–120000).

Module options (forRoot)

When you embed OpenBucket, pass these to OpenBucketModule.forRoot(...) instead of environment variables. Only dataDir and rootCredentials are required.

OpenBucketModule.forRoot({
dataDir: '/var/lib/openbucket',
rootCredentials: {
accessKeyId: process.env.OB_ACCESS_KEY!,
secretAccessKey: process.env.OB_SECRET_KEY!,
},
admin: {
username: 'admin',
passwordHash: process.env.OB_ADMIN_HASH!, // argon2id
jwtSecret: process.env.OB_JWT_SECRET!,
},
});

Top-level options

OptionRequiredDefaultNotes
dataDirSQLite DB + blob payloads + generated sse.key. Created on boot if absent.
rootCredentials{ accessKeyId, secretAccessKey } — the root SigV4 credential.
mountPath/storageRoute prefix for all OpenBucket routes. Path-style only; '' mounts at the root.
regionus-east-1Region reported to clients.
endpointDNS-safe hostname for endpoint discovery / presign defaults.
sseKeygeneratedbase64 of 32 bytes. Else generated + persisted to <dataDir>/sse.key.

admin — the admin surface

Omit the whole block to run a headless, S3-only store (no admin API, no JWT guard, no console). A present-but-partial block is rejected at boot.

FieldRequiredDefaultNotes
usernameBootstrap admin login.
passwordHashargon2id hash of the admin password.
jwtSecretStrong secret signing admin JWTs.
serveUitrueServe the bundled Angular SPA at <mountPath>/admin.
jwtAccessTtl900Access-token TTL in seconds.
jwtRefreshTtl604800Refresh-token TTL in seconds (7 days).

limits

FieldDefaultNotes
maxObjectSizeMb5120000Max single-object size in MiB.
maxMultipartParts10000Max parts per multipart upload.
multipartTtlHours24Abandoned-multipart TTL.

webhooks

Omit to disable HTTP webhook delivery (in-process @OnObject* handlers still work). Presence of url enables the transactional outbox + delivery runner.

FieldRequiredDefaultNotes
urlTarget endpoint. https required unless loopback.
secretStrong secret. HMAC-SHA256 signing key.
eventsall threeSubset of object.created / object.deleted / multipart.completed.
maxAttempts8Attempts before dead-letter.
timeoutMs5000Per-request timeout.
pollMs15000Delivery tick interval.

replication

Omit to disable. A present-but-partial block is rejected at boot (require bucket + both credentials).

FieldRequiredDefaultNotes
bucketRemote target bucket (must already exist).
credentials{ accessKeyId, secretAccessKey } for the target.
endpointS3-compatible endpoint. Omit for real AWS S3.
regionus-east-1Target region.
forcePathStyletruetrue for MinIO/S3-compat; false for AWS.
maxAttempts12Dead-letter cap.
drainIntervalMs5000Drain tick interval.
batchKeys50Distinct keys drained per tick.
largeObjectThresholdBytes67108864Stream via multipart above this (64 MiB).

backups

Omit to disable. Exactly one of cron / intervalMinutes must be set.

FieldDefaultNotes
scopeinstanceinstance = one snapshot; buckets = one per bucket.
cron5-field cron. Mutually exclusive with intervalMinutes.
intervalMinutesFixed interval (minutes). Mutually exclusive with cron.
dir<dataDir>/backupsAbsolute snapshot directory.
keepLast7Keep the newest N snapshots.
maxAgeDays30Also keep anything younger than this.
checkIntervalMs60000"Is a snapshot due?" wake tick.
pushToReplicationfalseAlso push each snapshot to the replication target.

metrics & tracing

FieldDefaultNotes
metrics.modeoffoff / public / token — gates <mountPath>/metrics.
metrics.tokenStrong bearer token — required when mode: 'token'.
tracing.enabledfalseOpenTelemetry span wrapping. No-op unless @opentelemetry/api + an SDK are present.
info
Static options in forRootAsync

mountPath, serveUi, and admin (the on/off switch) are static — passed alongside useFactory because routing is wired at module-config time. The admin secrets still come from the async factory. See the NestJS module reference.

Refuse-to-boot validation

Both entry points validate security-critical formats before serving a single request: a non-argon2id passwordHash, a too-short jwtSecret, a weak rootCredentials.secretAccessKey, a token-mode metrics endpoint behind a weak token, or an enabled-but-incomplete replication / backup block all abort startup with a precise message. Fail loud, never silently insecure.

Next steps