Docker

Docker

Docker

Production-ready compose snippet with Postgres + Dragonfly:

services:
  registry:
    image: ghcr.io/tabularisdb/tabularium:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      JWT_SECRET: ${JWT_SECRET:?run: openssl rand -hex 32}
      TOKEN_ENC_KEY: ${TOKEN_ENC_KEY:?run: openssl rand -hex 32}
      BASE_URL: "https://registry.example.com"
      DATA_DIR: "/data"
      DATABASE_URL: "postgres://registry:registry@postgres:5432/registry"
      CACHE_DRIVER: "redis"
      REDIS_URL: "redis://dragonfly:6379"
    volumes:
      - ./tabularium-data:/data
    depends_on:
      postgres: { condition: service_healthy }

  postgres:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: registry
      POSTGRES_PASSWORD: registry
      POSTGRES_DB: registry
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U registry"]
      interval: 5s

  dragonfly:
    image: docker.dragonflydb.io/dragonflydb/dragonfly
    restart: unless-stopped
    command: --proactor_threads=2
yaml

Before docker compose up -d, create the host folders so the bind mounts have something to attach to:

mkdir -p ./tabularium-data ./pgdata
bash

./tabularium-data holds the auto-generated bootstrap-password, config.json, the SQLite DB (if you ever switch), and uploads/. Back it up with the database.

Env vars

Required:

VarNotes
JWT_SECRET≥32 chars; must not be the placeholder change-me-in-production.
TOKEN_ENC_KEYExactly 64 hex chars (AES-256-GCM at-rest key for stored OAuth tokens and encrypted settings).
BASE_URLhttp(s)://… — public origin; used in OAuth callbacks and webhook URLs.

Optional:

VarDefaultNotes
WEB_BASE_URLBASE_URLSeparate origin for the frontend SPA if hosted elsewhere.
ALLOWED_ORIGINSComma-separated extra CORS origins. BASE_URL and WEB_BASE_URL are always allowed.
DATA_DIR./dataContainer path for the SQLite DB, uploads, bootstrap-password, config.json. Point this at the persistent volume.
DATABASE_URLNormally set via install wizard. Scheme picks the dialect (sqlite: / postgres:// / mysql://).
BOOTSTRAP_PASSWORD(auto-generated)Pins the first-run admin password. If unset, a random one is generated and written to $DATA_DIR/bootstrap-password (mode 0600).
CACHE_DRIVERmemorymemory, redis, or off.
REDIS_URLRequired when CACHE_DRIVER=redis. redis://… or rediss://….
NODE_ENVdevelopmentdevelopment / production / test.
PORT3000
LOG_LEVELinfo

TLS

Terminate TLS at a reverse proxy and forward to port 3000. Caddy is one line:

registry.example.com {
  reverse_proxy localhost:3000
}
null

The container serves the SPA, API (/api), OAuth callbacks (/auth), OpenAPI (/openapi), and uploads (/uploads) all under /. No path-based routing needed.

Backups

Snapshot DATA_DIR (config.json, SQLite DB if used, uploads/, bootstrap-password) plus — for Postgres/MySQL deployments — the external database. The uploads/ directory holds branding assets and provider logos.