Skip to content
Get started
Get started
bunqueue.config.ts: Typed Server Configuration File
View Markdown
server · configuration

Server configuration in one typed file.

Configure the whole bunqueue server from a single typed bunqueue.config.ts instead of scattered environment variables. Every option has IntelliSense, every section is optional.

This page is about configuring the standalone server (Server Mode). Embedded mode needs no config file, it takes options directly in the Queue/Worker constructors.

Create a bunqueue.config.ts in your project root:

import { defineConfig } from 'bunqueue';
export default defineConfig({
server: {
tcpPort: 6789,
httpPort: 6790,
},
storage: {
dataPath: './data/queue.db',
},
});

Then start normally:

bunqueue start

The config file is auto-discovered, no flags needed. defineConfig() gives you full TypeScript IntelliSense, so you never have to guess an option name.

When the same option is set in more than one place, the first of these wins:

  1. CLI flags, bunqueue start --tcp-port 8000
  2. Config file, bunqueue.config.ts
  3. Environment variables, TCP_PORT=8000
  4. Built-in defaults

Use the config file as your baseline and override per environment with env vars or flags.

bunqueue looks in your project root for bunqueue.config.ts, then bunqueue.config.js, then bunqueue.config.mjs. To use a specific file:

bunqueue start --config ./config/production.config.ts
# Short form
bunqueue start -c ./config/staging.config.ts

The server checks the whole file when it starts, before it binds a port:

  • A value the server cannot use stops startup with an error that names the key, for example timeouts.stats must be a finite number of milliseconds >= 1 (got 0) or auth.tokens must be an array of strings (got "secret"). Every problem is reported at once, together with invalid environment variables and CLI flags.
  • Numbers must be finite; a fractional value is rounded down to a whole number. Ports, timeouts.shutdown, timeouts.stats and the backup interval / retention also accept a numeric string, so tcpPort: process.env.PORT works.
  • Boolean keys take true or false. Any other value is read as earlier releases read it, by JavaScript truthiness, and logged as a warning: a non-empty string is true ('false' and '0' included) and '' is false. With enabled: process.env.S3_BACKUP_ENABLED, convert the variable to a boolean (process.env.S3_BACKUP_ENABLED === 'true').
  • null means “not set” for any key or section (bucket: process.env.S3_BUCKET ?? null).
  • Auth tokens must be non-empty strings: an empty or whitespace-only entry in auth.tokens stops startup, auth.tokens[0] must not be empty or whitespace-only (got ""), and so does a missing one, auth.tokens[0] must be a non-empty string (got undefined). Tokens are trimmed.
  • Values that earlier releases replaced with a default keep that default and are logged as a warning, for example a negative telemetry.maxPrometheusQueues (100) or an invalid storage.completedRetentionMs (retention off).
  • An unknown key (a typo such as completedRetentionMS, or a key from a newer version) does not stop the server: it is logged as a warning, Unknown config key "storage.completedRetentionMS" is ignored, and the setting keeps its default. defineConfig() already flags such keys in your editor.

defineConfig() itself stays a plain pass-through: the checks run when the server loads the file.

Every section is optional. Only specify what you need.

TCP and HTTP server settings.

defineConfig({
server: {
tcpPort: 6789, // TCP server port (default: 6789)
httpPort: 6790, // HTTP/REST API port (default: 6790)
host: '0.0.0.0', // Bind address (default: 0.0.0.0)
tcpSocketPath: undefined, // Reserved, not applied yet: TCP always binds host:port
httpSocketPath: undefined, // Unix socket for HTTP (overrides host/port)
tlsCertFile: undefined, // PEM certificate, enables native TLS on TCP + HTTP (with tlsKeyFile)
tlsKeyFile: undefined, // PEM private key (set both or neither, partial config is a startup error)
},
});

Authentication tokens for clients. Set this on any server reachable from a network.

defineConfig({
auth: {
tokens: ['my-secret-token'], // Auth tokens for TCP/HTTP
requireAuthForMetrics: false, // Require auth for /prometheus (env: METRICS_AUTH)
},
});

Every token must contain at least one non-whitespace character. An empty or whitespace-only token stops startup with an error naming the entry, because it would match a request that carries no credentials. Watch for a fallback such as tokens: [process.env.API_TOKEN ?? '']: it type-checks, and it yields [''] when the variable is unset, so the server refuses to start. tokens: [] is accepted and disables authentication, and auth.tokens takes precedence over AUTH_TOKENS. Tokens are trimmed on both sides of the comparison: a client that sends the same secret with a trailing newline still authenticates.

Where jobs persist. Memory and SQLite remain the defaults; PostgreSQL is an optional standalone-server backend for multiple active brokers.

defineConfig({
storage: {
driver: 'sqlite', // 'memory' | 'sqlite' | 'postgres'
dataPath: './data/queue.db', // required for explicit SQLite
maxCompletedJobs: 50_000, // completed-job hot cache/recovery window
completedRetentionMs: 7 * 24 * 60 * 60 * 1000, // optional durable retention
},
});

maxCompletedJobs bounds the in-memory completed-job projection (a whole number ≥ 1); it does not delete SQLite rows. Set completedRetentionMs to opt into age-based durable cleanup (up to 1,000 oldest eligible rows per 10-second cleanup tick). The default is null, so completed rows remain until queue.clean(...), obliterate, or another explicit policy removes them. Results still needed by live dependency consumers are protected until the consumer leaves the graph. Finite non-negative values are rounded down to whole milliseconds and null disables retention. A negative, non-finite or non-numeric value also disables retention, as in earlier releases, and the server logs a warning naming the key.

The server CLI equivalents are --max-completed-jobs and --completed-retention-ms; environment equivalents are documented in the environment reference.

Without driver, a PostgreSQL URL selects PostgreSQL, a data path selects SQLite, and neither selects in-memory storage. PostgreSQL configuration:

defineConfig({
storage: {
driver: 'postgres',
url: process.env.BUNQUEUE_POSTGRES_URL!,
namespace: 'production', // isolates installations sharing one database
brokerId: process.env.HOSTNAME, // unique per active broker; auto-generated if omitted
poolSize: 4, // default 4, runtime minimum 2
leaseDurationMs: 30_000, // default 30s, runtime minimum 1s
pollIntervalMs: 250, // durable event/cron fallback, minimum 25ms
statementTimeoutMs: 30_000,
lockTimeoutMs: 5_000,
idleTransactionTimeoutMs: 30_000,
maxConcurrentOperations: 16,
maxQueuedOperations: 128,
maxSnapshotJobs: 100_000,
maxSnapshotPayloadBytes: 256 * 1024 * 1024,
},
});

Do not combine url with dataPath. PostgreSQL support is server-only and is validated against PostgreSQL 15, 16, 17, and 18.6; embedded queues continue to use memory/SQLite. MySQL is not supported. See Storage backends.

Bound labelled Prometheus output independently from the exact global totals:

defineConfig({
telemetry: {
maxPrometheusQueues: 100, // 0 disables per-queue label series
},
});

The environment equivalent is METRICS_MAX_QUEUES. The default is 100; an invalid or negative value keeps 100 and logs a warning.

Allowed origins for browser access to the HTTP API.

defineConfig({
cors: {
origins: ['https://myapp.com', 'https://admin.myapp.com'],
},
});

origins also accepts a comma-separated string, like CORS_ALLOW_ORIGIN ('*', 'https://a.example,https://b.example'). An undefined, null or empty entry ([process.env.FRONTEND_URL!] with the variable unset) is dropped with a warning.

Automatic snapshots of the SQLite database to any S3-compatible storage (AWS, MinIO, Cloudflare R2). See S3 Backup.

defineConfig({
backup: {
enabled: true,
bucket: 'my-bunqueue-backups',
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
sessionToken: process.env.S3_SESSION_TOKEN, // Temporary credentials
region: 'eu-west-1', // Default: us-east-1
endpoint: undefined, // Custom S3 endpoint (MinIO, R2, etc.)
virtualHostedStyle: undefined, // Force bucket-in-host addressing
interval: 6 * 60 * 60 * 1000, // Backup interval in ms (default: 6h, minimum 60000)
retention: 7, // Backups to keep (default: 7, minimum 1)
prefix: 'backups/', // S3 key prefix (default: 'backups/')
},
});

The server also needs storage.dataPath (or a data-path environment variable); automatic backup is unavailable in in-memory and PostgreSQL modes. A backup that cannot run (no bucket or credentials, an interval under a minute, an invalid retention) does not stop the server: it logs S3 backup configuration invalid with the settings to fix and runs without backups. An invalid value is never used.

defineConfig({
timeouts: {
shutdown: 30000, // Graceful shutdown timeout in ms (default: 30000, 0 = do not wait)
stats: 300000, // Stats logging interval in ms (default: 300000, minimum 1)
},
});

The environment equivalents are SHUTDOWN_TIMEOUT_MS and STATS_INTERVAL_MS; the config file wins.

timeouts.worker and timeouts.lock are accepted but ignored: they never took effect, and the server logs a warning when they are present. Set the worker heartbeat freshness window with WORKER_TIMEOUT_MS (default 30000; a worker whose last heartbeat is older is shown as stale and is removed after three times that window) and the internal lock acquisition timeout with LOCK_TIMEOUT_MS (default 5000), both in milliseconds.

webhooks.maxRetries and webhooks.retryDelay are accepted but ignored: they never took effect, and the server logs a warning when they are present. Configure webhook delivery retries with WEBHOOK_MAX_RETRIES (delivery attempts per event, first try included, default 3, minimum 1) and WEBHOOK_RETRY_DELAY_MS (base delay in ms; attempt n + 1 waits n × the delay, default 1000).

defineConfig({
logging: {
level: 'info', // 'debug' | 'info' | 'warn' | 'error'
format: 'json', // 'text' | 'json'
},
});

Values are matched in any case, like the LOG_LEVEL and LOG_FORMAT environment variables they override, and the level also accepts warning, trace, verbose, fatal and critical. Any other value is logged as a warning and ignored. With the Docker image, LOG_FORMAT=json keeps JSON output even when the file says text.

import { defineConfig } from 'bunqueue';
export default defineConfig({
storage: { dataPath: './data/dev.db' },
logging: { level: 'debug' },
});
import { defineConfig } from 'bunqueue';
export default defineConfig({
server: { tcpPort: 6789, httpPort: 6790, host: '0.0.0.0' },
auth: {
tokens: [process.env.BUNQUEUE_AUTH_TOKEN!],
requireAuthForMetrics: true,
},
storage: { dataPath: '/data/bunqueue/queue.db' },
telemetry: { maxPrometheusQueues: 100 },
cors: { origins: [process.env.FRONTEND_URL!] },
backup: {
enabled: true,
bucket: process.env.S3_BUCKET!,
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: 'eu-west-1',
interval: 3600000, // Every hour
retention: 30,
},
logging: { level: 'info', format: 'json' },
timeouts: { shutdown: 60000 },
});

Mix the config file (static settings baked into the image) with environment variables (per-deployment values). Remember: when both define the same option, the config file wins.

// bunqueue.config.ts, static settings in the image
import { defineConfig } from 'bunqueue';
export default defineConfig({
server: { host: '0.0.0.0' },
logging: { format: 'json' },
backup: { enabled: true, region: 'eu-west-1' },
});
# Dynamic settings fill what the config file leaves unset
docker run \
-e TCP_PORT=6789 \
-e S3_BUCKET=my-bucket \
-e S3_ACCESS_KEY_ID=xxx \
-e S3_SECRET_ACCESS_KEY=xxx \
my-bunqueue-image

Available from both package exports:

import { defineConfig } from 'bunqueue';
// or
import { defineConfig } from 'bunqueue/client';
defineConfig({
cloud: {
url: 'https://cloud.bunqueue.io',
apiKey: process.env.BUNQUEUE_CLOUD_API_KEY,
instanceId: process.env.BUNQUEUE_CLOUD_INSTANCE_ID,
},
});