Deployment Model

A production BSB container starts with one active config plugin. That config plugin supplies the observable, events, and service plugin definitions. After config loads, BSB initializes and runs those plugins through the normal lifecycle.

  • Use one active config plugin: config-default, config-env, or config-vault.
  • Provide runtime state through mounted volumes and BSB_WRITABLE_PATHS.
  • Use prebuilt plugin directories or pinned startup installs for non-bundled plugins.
  • Prefer Forgejo image names in production. Docker Hub is a compatibility mirror.

Built-in defaults

The Node image includes @bsb/base, which provides config-default, config-env, events-default, and observable-default. These are useful defaults, not a separate deployment step. Use the registry when you need distributed events or external observability.

Config Plugin Selection

BSB_CONFIG_PLUGIN chooses the config plugin id. BSB_CONFIG_PLUGIN_PACKAGE tells BSB which package contains that plugin. Config plugin schema fields are read from env by exact, case-sensitive key names.

YAML File Config

Use config-default when production config is mounted as a YAML file.

services:
  bsb:
    image: code.bettercorp.dev/bettercorp/service-base:node
    environment:
      BSB_CONFIG_PLUGIN: config-default
      BSB_CONFIG_PLUGIN_PACKAGE: "@bsb/base"
      BSB_CONFIG_FILE: /app/sec-config.yaml
      BSB_PROFILE: default
    volumes:
      - ./sec-config.yaml:/app/sec-config.yaml:ro

Full JSON Env Config

Use config-env when the full runtime config is supplied by the environment instead of a mounted file.

services:
  bsb:
    image: code.bettercorp.dev/bettercorp/service-base:node
    environment:
      BSB_CONFIG_PLUGIN: config-env
      BSB_CONFIG_PLUGIN_PACKAGE: "@bsb/base"
      BSB_PROFILE: default
      BSB_CONFIG_JSON: >
        {"default":{"observable":{},"events":{},"services":{}}}

Vault Runtime Config

Use config-vault when the container should fetch its published runtime config from Vault. The container does not receive application, service group, or deployment profile names. The Vault API key is bound to those server-side.

services:
  bsb:
    image: code.bettercorp.dev/bettercorp/service-base:node
    environment:
      BSB_CONFIG_PLUGIN: config-vault
      BSB_CONFIG_PLUGIN_PACKAGE: "@bsb/config-vault"
      vaultUrl: https://vault.example.com
      apiKeyId: vk_xxx
      apiSecret: vs_xxx

Vault config plugin keys are case-sensitive: vaultUrl, apiKeyId, apiSecret, timeoutMs, and allowInsecureHttp.

Writable Runtime State

Use BSB_WRITABLE_PATHS for paths the entrypoint should create and make writable before dropping privileges. This is the correct way to prepare directories such as /data.

services:
  bsb:
    image: code.bettercorp.dev/bettercorp/service-base:node
    environment:
      BSB_WRITABLE_PATHS: /data,/cache
    volumes:
      - bsb-data:/data
      - bsb-cache:/cache

volumes:
  bsb-data:
  bsb-cache:

Plugins in Production

For deterministic production boots, prefer a prepared plugin directory or exact startup install versions.

Mounted Plugin Directory

services:
  bsb:
    image: code.bettercorp.dev/bettercorp/service-base:node
    environment:
      BSB_PLUGIN_DIRS: /mnt/plugins
    volumes:
      - ./plugins:/mnt/plugins:ro

Startup Plugin Install

Use BSB_PLUGINS when the container should install published npm plugin packages before BSB starts. Pin exact versions where possible.

services:
  bsb:
    image: code.bettercorp.dev/bettercorp/service-base:node
    environment:
      BSB_PLUGIN_DIRS: /mnt/plugins
      BSB_PLUGINS: "@myorg/service-api@1.2.3,@myorg/observable-custom@2.4.1"
    volumes:
      - bsb-plugins:/mnt/plugins

volumes:
  bsb-plugins:

@latest is rejected. Omit the selector for npm latest, or use major/minor/exact selectors. If startup install fails, the container exits before BSB starts.

When multiple containers share plugin storage, startup installs use package-scoped locks. Installing one package does not block unrelated package installs.

Shared Plugin Watcher

For shared storage, run one watcher container that keeps the plugin volume current. Runtime services mount the same volume read-only and do not install plugins during boot.

services:
  plugin-watcher:
    image: code.bettercorp.dev/bettercorp/service-base:node
    environment:
      BSB_PLUGIN_WATCHER: "true"
      BSB_PLUGIN_DIRS: /mnt/plugins
      BSB_PLUGINS: "@myorg/service-api@1.2,@bsb/observable-axiom@9.6"
      BSB_PLUGIN_WATCH_INTERVAL_SECONDS: 3600
    volumes:
      - bsb-plugins:/mnt/plugins

  bsb:
    image: code.bettercorp.dev/bettercorp/service-base:node
    environment:
      BSB_PLUGIN_DIRS: /mnt/plugins
      BSB_CONFIG_PLUGIN: config-vault
      BSB_CONFIG_PLUGIN_PACKAGE: "@bsb/config-vault"
      vaultUrl: https://vault.example.com
      apiKeyId: vk_xxx
      apiSecret: vs_xxx
    volumes:
      - bsb-plugins:/mnt/plugins:ro

volumes:
  bsb-plugins:

Use exact selectors for fully pinned deployments, minor selectors for patch updates, and major/no selector only when you intentionally want broader automatic updates. Services pick up newly synced packages on restart.

Vault Manager Service

The Vault manager service is itself a BSB service. Bootstrap it with config-env and enable service-config-vault from the bundled @bsb/config-vault package.

services:
  postgres:
    image: postgres:18
    environment:
      POSTGRES_DB: bsb_vault
      POSTGRES_USER: vault
      POSTGRES_PASSWORD: change-me
    volumes:
      - vault-postgres-data:/var/lib/postgresql/data

  vault:
    image: code.bettercorp.dev/bettercorp/service-base:node-latest
    environment:
      BSB_CONFIG_PLUGIN: config-env
      BSB_CONFIG_PLUGIN_PACKAGE: "@bsb/base"
      BSB_PROFILE: default
      BSB_CONFIG_JSON: >
        {"default":{"observable":{},"events":{},"services":{"service-config-vault":{"plugin":"service-config-vault","package":"@bsb/config-vault","enabled":true,"config":{"host":"0.0.0.0","port":8080,"publicUrl":"https://vault.example.com","production":true,"databaseUrl":"postgres://vault:change-me@postgres:5432/bsb_vault","masterKey":"BASE64_32_BYTE_KEY"}}}}}
    ports:
      - "8080:8080"

volumes:
  vault-postgres-data:

Do not add @bsb/config-vault to BSB_PLUGINS for the official Node image. It is already bundled.

Debugging Startup

  • BSB_SHOW_PACKAGES=true: print discovered packages, plugins, versions, paths, and config snippets.
  • BSB_DEBUG=true: enable production debug logging.
  • npm run plugins: run the package/plugin discovery report inside a container shell.

Build separately

This page covers deployment. For building a Node.js service image with FROM code.bettercorp.dev/bettercorp/service-base:node, use Node.js Docker Builds.