Skip to content

Docker Compose

The whole compose file, nothing to assemble

One service, the published ghcr.io image, and every environment variable Studio reads — active ones set, rarely-used ones sitting commented where you can find them. It is the same file that lives in the repository; this page just saves you the clone.

Quick start

Four lines from an empty directory to a login screen. The third writes the two secrets the file refuses to start without.

bash
curl -O https://libredb.org/docker-compose.example.yml
mv docker-compose.example.yml docker-compose.yml
printf 'ADMIN_PASSWORD=change-me\nJWT_SECRET=%s\n' "$(openssl rand -base64 32)" > .env
docker compose up -d

Studio is now on http://localhost:3000. Sign in with [email protected] and the password you just wrote, then point it at a database on the same network.

The full file

docker-compose.example.yml — 142 lines. Read the comments; they carry the parts that are easy to get wrong.

docker-compose.example.yml
download
# =============================================================================
# LibreDB Studio — Ready-to-use Docker Compose
# =============================================================================
# Pulls the published image (ghcr.io/libredb/libredb-studio:latest) — no source
# build required. Works on any Docker host and PaaS that consumes a plain
# docker-compose.yml (Dokploy, Coolify, Portainer, etc.).
#
# Quick start:
#   1. cp docker-compose.example.yml docker-compose.yml
#   2. cp .env.example .env        # then set at least JWT_SECRET / passwords
#   3. docker compose up -d
#   4. open http://localhost:3000
#
# Every supported environment variable is listed below. Commonly-used ones are
# active; less-frequently-used ones are shown commented out — uncomment as needed.
# Secrets are read from the .env file via ${VAR} interpolation and are never
# hardcoded in this file.
# =============================================================================

services:
  libredb-studio:
    image: ghcr.io/libredb/libredb-studio:latest
    container_name: libredb-studio
    restart: unless-stopped
    ports:
      - "3000:3000"
    # Uncomment if you enable the bundled PostgreSQL service below:
    # depends_on:
    #   libredb-postgres:
    #     condition: service_healthy
    environment:
      # -----------------------------------------------------------------------
      # AUTHENTICATION — local provider. ADMIN_PASSWORD is required (without it
      # the login screen shows a clear config error); USER_* is optional.
      # -----------------------------------------------------------------------
      ADMIN_EMAIL: ${ADMIN_EMAIL:[email protected]}        # admin: full access + maintenance tools
      ADMIN_PASSWORD: ${ADMIN_PASSWORD:?set ADMIN_PASSWORD in .env}
      # Optional lower-privilege account (query execution only). Omit USER_PASSWORD
      # to run admin-only — no default user password is ever assumed.
      USER_EMAIL: ${USER_EMAIL:[email protected]}
      USER_PASSWORD: ${USER_PASSWORD:-}
      # JWT signing secret — min 32 chars. Generate: openssl rand -base64 32
      JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env (min 32 chars)}

      # Auth provider: "local" (default, email/password) or "oidc" (SSO)
      NEXT_PUBLIC_AUTH_PROVIDER: ${NEXT_PUBLIC_AUTH_PROVIDER:-local}

      # -----------------------------------------------------------------------
      # OIDC SSO  (only when NEXT_PUBLIC_AUTH_PROVIDER=oidc)
      # Auth0 / Keycloak / Okta / Azure AD / Zitadel
      # -----------------------------------------------------------------------
      # OIDC_ISSUER: ${OIDC_ISSUER}                # must serve /.well-known/openid-configuration
      # OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
      # OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
      # OIDC_SCOPE: ${OIDC_SCOPE:-openid profile email}
      # OIDC_ROLE_CLAIM: ${OIDC_ROLE_CLAIM:-}      # e.g. realm_access.roles (Keycloak), groups (Okta)
      # OIDC_ADMIN_ROLES: ${OIDC_ADMIN_ROLES:-admin}

      # -----------------------------------------------------------------------
      # STORAGE — where connections/config are persisted (server-side)
      #   "local"    (default) browser localStorage, zero config
      #   "sqlite"   server file, single-node persistent (uncomment volume below)
      #   "postgres" multi-node persistent (uncomment the postgres service below)
      # -----------------------------------------------------------------------
      STORAGE_PROVIDER: ${STORAGE_PROVIDER:-local}
      # STORAGE_SQLITE_PATH: ${STORAGE_SQLITE_PATH:-/app/data/libredb-storage.db}
      # STORAGE_POSTGRES_URL: ${STORAGE_POSTGRES_URL:-postgresql://postgres:postgres@libredb-postgres:5432/libredb_storage?sslmode=disable}

      # -----------------------------------------------------------------------
      # AI / LLM  (optional) — provider: gemini | openai | ollama | custom
      # -----------------------------------------------------------------------
      # LLM_PROVIDER: ${LLM_PROVIDER:-gemini}
      # LLM_API_KEY: ${LLM_API_KEY}                # required for gemini/openai
      # LLM_MODEL: ${LLM_MODEL:-gemini-2.5-flash}
      # LLM_API_URL: ${LLM_API_URL}                # ollama/custom only, e.g. http://host:11434/v1

      # -----------------------------------------------------------------------
      # AGENT RUNTIME — standalone only, and there is no flag to turn it on.
      # It appears once a model is configured through the LLM_* settings above
      # (there is no second place for an API key) AND the ledger path below is
      # writable. Uncomment the line below to keep the AI configuration and have
      # no agent — including on an upgrade, where an LLM_API_KEY that used to
      # power NL2SQL now brings an agent with it.
      # -----------------------------------------------------------------------
      # LIBREDB_AGENT_ENABLED: "false"
      # Durable backend for run state. "local" (default) keeps it on disk behind
      # file locks — SINGLE INSTANCE ONLY. More than one replica needs
      # "@workflow/world-postgres" plus WORKFLOW_POSTGRES_URL.
      # WORKFLOW_TARGET_WORLD: ${WORKFLOW_TARGET_WORLD:-local}
      # Where the "local" backend writes, and the second half of the availability
      # answer above: if it cannot be created and written, the agent reports
      # itself absent. The image already defaults this to /app/data/workflow, so
      # uncomment it only to put the ledger elsewhere — and mount the data volume
      # below either way, or runs vanish when the container is recreated.
      # WORKFLOW_LOCAL_DATA_DIR: ${WORKFLOW_LOCAL_DATA_DIR:-/app/data/workflow}

      # -----------------------------------------------------------------------
      # SEED CONNECTIONS (optional) — pre-configure databases on boot.
      # Uncomment the seed volume mount below, then provide seed-connections.yaml.
      # Credentials referenced in that file via ${VAR} are read from this .env.
      # -----------------------------------------------------------------------
      # SEED_CONFIG_PATH: /app/config/seed-connections.yaml
      # SEED_CACHE_TTL_MS: ${SEED_CACHE_TTL_MS:-60000}

    # volumes:
    #   # SQLite storage persistence (STORAGE_PROVIDER=sqlite):
    #   - libredb-data:/app/data
    #   # Seed connections file (read-only):
    #   - ./seed-connections.yaml:/app/config/seed-connections.yaml:ro

    # Image runs as node:24.16.0-trixie-slim (no curl/wget) — use Node's built-in fetch.
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://localhost:3000/api/db/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

  # ---------------------------------------------------------------------------
  # Optional: PostgreSQL backend for STORAGE_PROVIDER=postgres
  # To enable: uncomment this service, the STORAGE_POSTGRES_URL env above,
  # the depends_on block above, and the pgdata volume below.
  # ---------------------------------------------------------------------------
  # libredb-postgres:
  #   image: postgres:18
  #   container_name: libredb-postgres
  #   restart: unless-stopped
  #   environment:
  #     POSTGRES_USER: postgres
  #     POSTGRES_PASSWORD: postgres
  #     POSTGRES_DB: libredb_storage
  #   volumes:
  #     - pgdata:/var/lib/postgresql/data
  #   healthcheck:
  #     test: ["CMD-SHELL", "pg_isready -U postgres"]
  #     interval: 10s
  #     timeout: 5s
  #     retries: 5

# volumes:
#   libredb-data:   # SQLite storage persistence
#   pgdata:         # PostgreSQL storage persistence

Every environment variable

Grouped as the file groups them. Set means the line ships uncommented and is already in force; commented means it is there for you to uncomment. Secrets come from .env through ${VAR} interpolation and are never written into the compose file.

Authentication

The local email/password provider. Studio will not start a usable login without ADMIN_PASSWORD and JWT_SECRET.

Variable Default In the file What it does
ADMIN_EMAIL [email protected] set Admin login — full access plus the maintenance tools.
ADMIN_PASSWORD required set Admin password. Without it the login screen shows a configuration error instead of a form.
USER_EMAIL [email protected] set Optional lower-privilege account — query execution only, no maintenance.
USER_PASSWORD empty set Leave it unset to run admin-only. No default user password is ever assumed.
JWT_SECRET required set Signing secret, minimum 32 characters. Generate one with openssl rand -base64 32.
NEXT_PUBLIC_AUTH_PROVIDER local set local for email/password, oidc to hand sign-in to your identity provider.

OIDC single sign-on

Only read when NEXT_PUBLIC_AUTH_PROVIDER=oidc. Tested against Auth0, Keycloak, Okta, Microsoft Entra ID and Zitadel.

Variable Default In the file What it does
OIDC_ISSUER commented Issuer URL. It must serve /.well-known/openid-configuration.
OIDC_CLIENT_ID commented Client ID from your identity provider.
OIDC_CLIENT_SECRET commented Client secret for that client.
OIDC_SCOPE openid profile email commented Scopes requested at sign-in.
OIDC_ROLE_CLAIM empty commented The claim that carries roles — realm_access.roles on Keycloak, groups on Okta.
OIDC_ADMIN_ROLES admin commented Which of those roles get admin access.

Storage

Where saved connections and configuration live. The default keeps them in the browser, so a fresh container starts empty on purpose.

Variable Default In the file What it does
STORAGE_PROVIDER local set local (browser storage, zero config), sqlite (one server file), or postgres (shared across replicas).
STORAGE_SQLITE_PATH /app/data/libredb-storage.db commented Where the SQLite file goes. Mount the data volume too, or it dies with the container.
STORAGE_POSTGRES_URL commented Connection string for the postgres provider. The file ships a matching optional postgres:18 service.

AI model

Optional. Configure a model here and natural-language querying appears; leave it out and Studio is a plain editor.

Variable Default In the file What it does
LLM_PROVIDER gemini commented gemini, openai, ollama or custom.
LLM_API_KEY commented Required for gemini and openai. This is the only place Studio reads a model key from.
LLM_MODEL gemini-2.5-flash commented Model name.
LLM_API_URL commented Base URL for ollama and custom, e.g. http://host:11434/v1.

Agent runtime

There is no switch that turns the agent on. It appears once a model is configured above and the ledger path is writable — which means an upgrade can hand an agent to a key you added for natural-language querying.

Variable Default In the file What it does
LIBREDB_AGENT_ENABLED unset commented Set to "false" to keep the AI configuration and have no agent.
WORKFLOW_TARGET_WORLD local commented Durable backend for run state. local is file-locked and single-instance; more than one replica needs @workflow/world-postgres.
WORKFLOW_LOCAL_DATA_DIR /app/data/workflow commented Where the local backend writes. If it cannot be created and written, the agent reports itself absent.

Seed connections

Optional. Ship the team a Studio that already knows the databases, instead of asking everyone to add them by hand.

Variable Default In the file What it does
SEED_CONFIG_PATH /app/config/seed-connections.yaml commented A mounted YAML file of connections to register on boot. Credentials inside it interpolate from the same .env.
SEED_CACHE_TTL_MS 60000 commented How long seeded connections are cached.