Production Deployment
Run BSB services in production with Docker. Choose one config source, provide plugin packages, and mount writable runtime state explicitly.
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, orconfig-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:roFull 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_xxxVault 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:roStartup 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.