DBMojo Docs

Operations

Docker Deployment

dbmojo-deploy

A repeatable, single-hostname install of the whole suite. You supply one hostname; Core and all five tools are served behind it, every hop over TLS, with secrets and certificates generated for you. The only thing the host needs is Docker with the Compose plugin. This page covers installing, configuring, upgrading, and scaling the stack.

What you get

Everything is served from one hostname. Each tool lives under its own URL prefix and Core owns the root:

URLServes
https://HOST/Core — admin UI, login, SSO callbacks, /api
https://HOST/detanamiDetanami
https://HOST/datamosaicDataMosaic
https://HOST/flowFlow — UI, API, and webhooks at /flow/webhook/*
https://HOST/blueprintBlueprint — the data modeler
https://HOST/smithSmith — UI, API, and previews at /smith/preview/*
https://HOST/api-gw/<api>Data APIs served by Smith API workers (only when enabled)

How the pieces fit together:

  • nginx terminates the browser's TLS and re-encrypts to every service, verifying each one against the stack's internal certificate authority. The URL prefix is passed through, never stripped — each app serves itself under its own prefix, so nginx never rewrites a response body. WebSockets are upgraded.
  • Single sign-on works because every app shares the same session secret, session store (a file on the shared volume), and cookie domain. The session cookie is marked Secure.
  • Two URLs per service. Browsers are sent to the public origin; servers talk to each other over the container network, so nothing depends on the containers being able to reach your public hostname.
  • No JSON is edited. All configuration reaches the apps as environment variables, driven by one .env file.
  • Core never connects to a target database. Deploying in containers doesn't change that.

The stack runs seven images: the six DBMojo ones (core, detanami, datamosaic, flow, smith, blueprint) and stock nginx:1.27-alpine. Enabling Flow workers adds redis:7-alpine.

Prerequisites

  • Docker with the Compose plugin on the host. That is the only host requirement — secrets and certificates are generated inside the Core image, so the host needs no OpenSSL or Node.
  • A hostname that resolves to the host (for a trial you can use localhost; see local testing). A bare IPv4 address also works.
  • Free ports 443 and 80 (or pick others with --https-port and HTTP_PORT).
  • The dbmojo-deploy directory. If you will build images from source instead of pulling them, check out these repositories side by side in the same parent folder: dbmojo-deploy, dbmojo-core, dbmojo-detanami, dbmojo-datamosaic, dbmojo-flow, dbmojo-smith, dbmojo-blueprint, dbmojo-auth-client, and dbmojo-llm-client. If you pull prebuilt images (see Images), only dbmojo-deploy is needed.

Quick start

From inside the dbmojo-deploy directory:

./install.sh --host dbmojo.example.com        # Linux / macOS / WSL / Git Bash
.\install.ps1 -HostName dbmojo.example.com    # Windows PowerShell with Docker Desktop

The installer does the following, in order:

  1. Creates .env from .env.example and records your hostname and the resulting public origin.
  2. Works out where the images come from — already in your local Docker, pulled from a registry, or built from the sibling source checkouts (see Images).
  3. Generates any missing secrets, an internal certificate authority, one certificate per service, and an edge certificate for your hostname (see Certificates).
  4. Runs docker compose up -d and prints the URLs.
Safe to re-run Running the installer again keeps your existing secrets, CA, and certificates. It never rebuilds images you already have unless you pass --rebuild or --pull.

When it finishes, open https://dbmojo.example.com/ and sign in as described below. Because a fresh install uses a certificate from the internal CA, browsers warn until you trust certs/ca.crt or supply your own certificate — see Certificates & TLS.

First login

The first admin account is named admin. On a first install the installer generates its password and writes it to .env, so it is always recoverable:

grep ADMIN_PASSWORD .env                          # Linux / macOS / WSL / Git Bash
Select-String -Path .env -Pattern ADMIN_PASSWORD   # PowerShell

Change the password after logging in. What the .env value does and doesn't mean:

  • It is used only when Core creates the account. Changing ADMIN_PASSWORD later does nothing, and once you change the password in the UI the .env value is stale — treat it as the initial password.
  • It is generated only on a first install (detected by the internal CA not existing yet). An existing install keeps whatever password its admin already has.
  • To choose your own, set ADMIN_PASSWORD=... in .env before the first start. It must be at least 8 characters; a shorter value stops Core from starting, with the reason in its log.
  • Leave it blank and Core generates a random password and prints it once in docker compose logs core — it is lost if the container is recreated.

New users, groups, and per-tool roles are then managed in Core's User Management, exactly as in a non-container install. See the Core reference.

Configuration (.env)

Deployment-level settings live in one file, dbmojo-deploy/.env, created by the installer. Edit it, never commit it. Most values are generated for you; the ones you are expected to set are the hostname and, for a real deployment, the image source and any license values.

VariableDefaultWhat it does
DBMOJO_HOST—The hostname users type, with no scheme or path. Set by --host. Also the session-cookie domain.
DBMOJO_PUBLIC_ORIGINderivedThe full external origin (https://HOST, plus :PORT when not 443). Rewritten by the installer on every run from the host and HTTPS port — don't edit it by hand.
HTTPS_PORT443The published HTTPS port.
HTTP_PORT80The published HTTP port, which only redirects to the HTTPS origin.
HSTS_MAX_AGE31536000HSTS lifetime in seconds. Set 0 on a throwaway hostname so browsers don't pin it.
SESSION_SECRETgeneratedShared by every app; this is what makes single sign-on work.
INTERNAL_SECRETgeneratedShared by every app; authenticates server-to-server calls. If you set your own, Core refuses to start unless it is at least 32 characters.
ENCRYPTION_KEYgeneratedProtects Core's connection vault. Back it up — losing it makes stored connection credentials unreadable.
ADMIN_PASSWORDgenerated on first installThe first admin's initial password. See First login.
JOIN_TOKEN_DETANAMI, _DATAMOSAIC, _FLOW, _SMITH, _BLUEPRINTblankPer-tool tokens that let a tool register with Core. Written by --register-tools. See Registering tools.
FLOW_ROLEallall runs flows inside flow-web; web hands them to worker containers. Set by --workers.
FLOW_WORKERS2Number of Flow worker containers when FLOW_ROLE=web.
REDIS_PASSWORDgeneratedPassword for the Flow workers' TLS-only Redis.
API_WORKERS0Number of Smith API worker containers. Set by --api-workers.
API_POOL_SIZE4Database connections each API worker opens per API.
COMPOSE_PROFILESderivedWhich optional services run (workers, apiworkers). Derived by the installer from the two worker settings — don't edit by hand on Linux/macOS.
DBMOJO_REGISTRYblankRegistry prefix to pull prebuilt images from. Blank builds from sibling source checkouts. See Images.
DBMOJO_VERSIONlatestImage tag to use. Pin a real version in production.
LICENSE_MODE, LICENSE_TOKEN, LICENSE_SERVICE_URL, LICENSE_PUBLIC_KEYblankPassed straight through to Core; blank uses Core's defaults. For an offline signed-file license, put license.json in the core-data volume and set LICENSE_PUBLIC_KEY; for a connected license set the mode, token, and service URL. See the Core reference.
Two kinds of configuration .env holds deployment settings. Suite-wide behaviour — the AI DBA Agent backend, enterprise sign-in providers, audit retention, shared connections — is still configured at runtime in Core's admin UI and stored in Core's database, exactly as in a non-container install.

Per-service settings such as ports and base paths are set in docker-compose.yml and normally don't need to change.

Installer options

install.shinstall.ps1Effect
--host NAME-HostName NAMESet the hostname. Required on the first run.
--https-port N-HttpsPort NPublish HTTPS on a port other than 443; the public origin then includes it.
--renew-certs-RenewCertsReissue the internal service certificates. See Certificates.
--no-start-NoStartPrepare .env, images, and certificates but don't start the stack.
--rebuild-RebuildForce building images from source, e.g. after updating the code.
--pull-PullForce pulling images; needs DBMOJO_REGISTRY set.
--workers Nnot availableRun Flow workers. 0 turns them off. See Flow workers.
--api-workers Nnot availableRun Smith API workers. 0 turns them off. See API workers.
--register-tools LISTnot availableInstall tools in Core and hand each its join token. all or a comma-separated list. See Registering tools.

The options marked not available are only in the shell installer. On Windows, run install.sh from Git Bash or WSL, or set the equivalent .env values by hand — see Windows notes.

Certificates & TLS

Every hop is encrypted: browser to nginx, and nginx to each service. The installer creates and manages the certificates for the internal hops; you only need to think about the one browsers see.

WhatWhereNotes
Internal CAcerts/ca.crt, certs/ca.keyValid ten years. ca.key is never mounted into a container — keep it private.
Service certificatescerts/<service>/tls.crt, tls.keyOne per service, valid 825 days, with the service name as its SAN.
Edge certificate (what browsers see)certs/edge/tls.crt, tls.keySee below.

Production: use your own certificate

Put your certificate (full chain) and key at certs/edge/tls.crt and certs/edge/tls.key before running the installer. To swap one in later, replace the files and run docker compose restart nginx.

If you don't, the installer issues an edge certificate from the internal CA and browsers warn until they trust certs/ca.crt. Air-gapped installs work the same way: nothing in the install calls out to the internet.

Renewal

./install.sh --renew-certs

This reissues the internal service certificates (and the edge certificate only if it was issued internally, never one you supplied), then you restart with docker compose restart. The installer warns whenever a certificate is within 30 days of expiry.

Registering tools with Core

A tool is running as soon as the stack is up, but it stays hidden from Core's launcher until an admin installs it in Core (System Management → Tools). Installing issues a join token, shown once. A tool given its token calls Core when it boots, and Core then shows it as joined, when it last booted, and the URL browsers use for it.

To do the whole handoff in one step — install in Core, capture each token, save it to .env, and restart the tool so it joins:

./install.sh --register-tools all              # or: --register-tools flow,smith,blueprint
scripts/register-tools.sh [--rotate] [tool ...]   # any time later
  • It is opt-in. Installing a tool makes it visible to users, and you may deliberately skip some, so nothing is registered unless you ask.
  • Tokens are shown once and Core keeps only a hash. An already-installed tool's token can't be recovered, so the script skips it unless you pass --rotate, which issues a new token and invalidates the old one.
  • It needs the admin password — via --password, the REGISTER_ADMIN_PASSWORD environment variable, or the ADMIN_PASSWORD in .env from a first install.
  • Tokens live in .env as JOIN_TOKEN_<TOOL>; the script never prints them. To do it by hand instead: install the tool in Core's UI, paste the token into .env, then docker compose up -d <tool>.
  • Needs a POSIX shell. On Windows use Git Bash or WSL.

What a tool does when it boots: it joins once per boot (so "last seen" means "last booted", not "up right now"); it reports its public URL, never its container address; a refusal from Core is final (it logs why once and stops asking) while an outage is retried with backoff, so a tool that boots before Core still registers; and a tool with no token simply logs one line and carries on serving. Worker containers never join — only the tools themselves do.

Images & upgrades

The compose file names the DBMojo images ${DBMOJO_REGISTRY:-dbmojo}/{core,detanami,datamosaic,flow,smith,blueprint}:${DBMOJO_VERSION:-latest}. The Flow image is called flow, while its compose service is flow-web. The installer picks the image source in this order:

  1. Already local. If every image is in the local Docker (built earlier, or loaded with docker load) it uses them and touches nothing. This is what makes air-gapped installs work.
  2. Registry. If DBMOJO_REGISTRY is set in .env, it runs docker compose pull.
  3. Build. Otherwise it runs docker compose build from the sibling source checkouts.

--pull and --rebuild force one path. Images contain code only — no secrets, certificates, or hostname — so one image set serves every customer.

Option A — publish to a registry

On a build machine with all repositories checked out side by side and docker login done:

DBMOJO_REGISTRY=registry.example.com/dbmojo DBMOJO_VERSION=1.4.0 \
  ./scripts/publish.sh --also-latest

This builds the five images for linux/amd64 and pushes them tagged with the version. Both variables are required, and latest is refused as a version — pin real versions, and add --also-latest only if you want a moving tag too. Set PLATFORM=linux/arm64 if your servers are ARM (an ARM build machine would otherwise produce images a typical server can't run). --no-push builds and tags without pushing.

On the target host you need only the dbmojo-deploy directory:

cp .env.example .env
#   set in .env:
#     DBMOJO_REGISTRY=registry.example.com/dbmojo
#     DBMOJO_VERSION=1.4.0
docker login registry.example.com        # if the registry is private
./install.sh --host dbmojo.example.com   # a registry is set, so it pulls

nginx:1.27-alpine comes from Docker Hub unless you mirror it.

Option B — air-gapped host

On a connected machine that already has the images, build one self-contained tarball:

DBMOJO_VERSION=1.4.0 DBMOJO_REGISTRY=registry.example.com/dbmojo \
  ./scripts/bundle.sh dist
# -> dist/dbmojo-1.4.0.tar.gz and dist/dbmojo-1.4.0.tar.gz.sha256

It contains all six images (including nginx) plus the compose file, nginx config, scripts, and installers. The bundled .env.example already carries the matching registry and version, so the host can't end up looking for different tags. Carry the tarball and its checksum across, then on the host:

sha256sum -c dbmojo-1.4.0.tar.gz.sha256
tar xzf dbmojo-1.4.0.tar.gz && cd dbmojo-1.4.0
docker load -i images.tar.gz             # prints each "Loaded image: ..."
./install.sh --host dbmojo.example.com   # all images local: no pull, no build

If you enable Flow workers on an air-gapped host, redis:7-alpine must be loaded too. If you hand-roll docker save instead of using the bundle script, save everything listed by docker compose config --images.

Upgrading

Change DBMOJO_VERSION in .env, then run ./install.sh --pull (or docker compose pull && docker compose up -d). Volumes, secrets, and certificates are untouched. Core and every tool keep state in local SQLite and run as a single instance each, so expect a short outage while containers restart — take a backup first.

Flow workers

By default Flow runs every deployed flow inside the flow-web container. To run flows on separate worker containers with one central schedule:

./install.sh --workers 2                       # 2 workers + a TLS-only Redis; flow-web becomes the coordinator
./install.sh --workers 0                       # back to running flows inside flow-web
docker compose up -d --scale flow-worker=4     # change the worker count later

It is safe on an existing install: deployed flows move to the workers on their own, and --workers 0 brings them back. Flow's users and the Deploy, Stop, and Trigger buttons work as before.

  • A flow is placed, not split. A deployed flow always runs on exactly one process. Scale comes from spreading different flows across workers — one flow doesn't get faster with more workers.
  • New workers take new flows. Running flows aren't rebalanced onto them, because moving a flow restarts it and loses its in-memory state. If a worker disappears, its flows move to another.
  • One clock. flow-web owns every cron and interval trigger and pushes each firing to the owning worker. While it is down, scheduled firings stop (flows already running keep running).
  • Firings are at-most-once. A firing isn't retried if a worker crashes mid-run, and one older than FLOW_MAX_JOB_AGE seconds (default 60) is dropped rather than run late.
  • Secrets stay off Redis. Redis holds only coordination state, is TLS-only and password-protected, and persists nothing. Workers fetch flow definitions from flow-web over authenticated TLS.
  • Flows with an HTTP In node stay on flow-web, because webhooks arrive there.

Limits: failover takes about 15–20 seconds after a worker is killed outright (about 4 seconds after a clean stop); workers must share a host because they share two volumes with flow-web; and a TCP In node in listen mode opens a port inside its worker's container that nothing publishes. As an admin, GET /flow/api/cluster shows the live workers, each flow's owner, and the schedule.

Smith API workers

Smith can serve the Data APIs it generates from separate worker containers behind the proxy, rather than only offering a local preview or an AWS Lambda deploy:

./install.sh --api-workers 2                    # 2 api-worker containers
./install.sh --api-workers 0                    # turn them off
docker compose up -d --scale api-worker=4       # change the count later

With workers on, Smith's Deploy dialog gains an API workers target, and a deployed API is served at https://HOST/api-gw/<api> (the API's URI slug, or its id). The Flow and API worker settings are independent — turning one on or off never changes the other.

  • Stateless. Every worker serves every API; nginx spreads requests across replicas and retries another if one dies. Workers poll Smith every few seconds, so changes (a stop, a key rotation, a renamed slug) reach them within about 5 seconds.
  • A deploy is a snapshot. Editing the notebook afterwards changes nothing live until you deploy again.
  • Same approval rules as Lambda. A connection imported from Core's shared vault is held for a suite admin's approval first; nothing is served while it waits.
  • The database credential travels over authenticated TLS, never through Redis or an image.
Every API requires a key There is no anonymous mode. Deploying issues an access key, shown once (only its hash is stored). Callers send it as Authorization: Bearer <key> or X-API-Key: <key>; a missing or wrong key is a 401, and a Core login cookie is not accepted. A redeploy keeps the same key. A lost key can't be recovered — rotate it with POST /smith/api/deploy/worker/<id>/rotate-key as a logged-in user.

Limits to plan for: each worker opens its own connection pool per API, so total connections to your database are workers × API_POOL_SIZE × APIs — size that against the database's connection limit. There is no rate limiting or per-caller identity yet (one key per API), so put a rate limit in front if an API is exposed beyond trusted callers. SQLite connections can't be served. A licensed Core is required for the shared-connection and approval path; a fresh Core with no license answers it with a 402.

Running & backing up

Everyday commands, run from the dbmojo-deploy directory:

docker compose ps                      # service status and health
docker compose logs -f core            # follow one service's log
docker compose restart nginx           # e.g. after replacing the edge certificate
docker compose stop                    # stop the stack, keeping all data
docker compose up -d                   # start it again
Don't use down -v docker compose down removes containers but keeps your data. Adding -v also deletes the named volumes — and with them every user, connection, flow, and notebook.

What to back up

The stack stores its state in named Docker volumes:

VolumeHolds
sharedSession store (sessions.db)
core-dataCore's database, audit log, and license
detanami-dataDetanami's data
datamosaic-data, datamosaic-uploadsDataMosaic's data and uploaded files
flow-data, flow-files, flow-custom-nodesFlows and their credential key, File In/Out files, and custom node files
smith-data, smith-generatedSmith's notebooks (including its key) and generated API code
blueprint-dataBlueprint's models

Back up all of the volumes, plus .env (it holds ENCRYPTION_KEY and INTERNAL_SECRET) and the certs/ directory. Because each service keeps its state in SQLite, stop the stack (docker compose stop) before copying the volumes so the files are consistent. Volume names are prefixed with the compose project name, dbmojo (for example dbmojo_core-data).

Windows & local testing

Trying it on one machine

Use a throwaway hostname and port so nothing collides with other dev servers:

./install.sh --host localhost --https-port 8443
#   and in .env:   HTTP_PORT=8080   HSTS_MAX_AGE=0

Setting HSTS_MAX_AGE=0 matters: browsers pin HSTS per hostname regardless of port, so a long lifetime on localhost would force HTTPS onto every other http://localhost:... server you run.

Windows notes

  • install.ps1 needs Docker Desktop and has no worker or registration options. For those, use install.sh from Git Bash or WSL.
  • To enable workers with only the PowerShell installer, set the values in .env and run docker compose up -d: for Flow workers, FLOW_ROLE=web and FLOW_WORKERS=N; for API workers, API_WORKERS=N; and in either case set COMPOSE_PROFILES to workers, apiworkers, or workers,apiworkers to match.
  • nginx renders its config from a template only when its container starts, and install.sh force-recreates it on every run to pick up changes; install.ps1 does not. After updating dbmojo-deploy to a version with new nginx routes, run docker compose up -d --force-recreate nginx.
  • Windows curl rejects a private CA because it can't check revocation. Add --ssl-no-revoke alongside --cacert certs/ca.crt.
  • Building images on Windows goes through Docker Desktop and produces Linux images; nothing Windows-specific ends up in them.

Troubleshooting

SymptomCause and fix
The browser warns about the certificateThe edge certificate came from the internal CA. Trust certs/ca.crt, or supply your own at certs/edge/ — see Certificates.
A tool is running but missing from Core's launcherIt hasn't been installed in Core. Run scripts/register-tools.sh or install it under System Management → Tools. See Registering tools.
Core won't start and mentions the admin passwordADMIN_PASSWORD is set but shorter than 8 characters. Lengthen it, or blank it to have Core generate one. Check docker compose logs core.
The installer tries to pull or build when you expected it not toDBMOJO_REGISTRY and DBMOJO_VERSION in .env must match the local image tags exactly. Compare docker compose config --images against docker images.
Images won't start on the serverImages are built for linux/amd64 by default. For an ARM server publish with PLATFORM=linux/arm64.
Typing http://host:8443 gives an errorPlain HTTP on the TLS port is redirected to the configured HTTPS origin; if you still see an error, check DBMOJO_PUBLIC_ORIGIN and re-run the installer.
/api-gw/... returns 502 or 503No API workers are running. Enable them with --api-workers N.
Every tool stops working at onceThe Core license has expired — every tool depends on it. See the license values in Configuration and the Core reference.
Flow or Detanami crash-loop after a custom image buildNative modules (better-sqlite3, sqlite3) must compile inside the image. The Dockerfile sets npm_config_build_from_source for this; don't remove it.