Deploy with docker compose

Run the team server (and the runner) on one host with the single, ha-mongodb or ha-ferretdb compose profile.

deploy/compose/ has three profiles, one compose file each, sharing hardened service templates (common.yaml). Each puts Caddy in front for TLS.

ProfileFileWhat runs
singlecompose.single.yamlone team server on SQLite (named volume) + Caddy
ha-mongodbcompose.ha-mongodb.yamla 3-member MongoDB replica set, 2 team servers, 2 runners (database mode), Caddy load-balancing both
ha-ferretdbcompose.ha-ferretdb.yamlPostgreSQL with DocumentDB, 2 FerretDB 2, 2 team servers, 2 runners, Caddy

Requirements: Docker Engine 24+ with Compose v2.24+ (or v5), and the oxysdb-server and (HA) oxysdb-runner images: build them with deploy/test/images-build.sh (tags :dev) or pull them from your registry, and name them in .env.

Single node

cd deploy/compose
cp .env.example .env                      # optional: images, host name, ports
docker compose -f compose.single.yaml up -d
docker compose -f compose.single.yaml ps  # server and caddy "healthy"

# First account; the password is the first line of standard input (at least 8 characters)
printf '%s\n' 'a long passphrase' | \
  docker compose -f compose.single.yaml exec -T server oxysdb-server add-account --email [email protected]

The server answers on https://localhost:8443. Enter that address when you sign in. With the default OXYSDB_TLS=internal, the certificate comes from Caddy’s own CA; export its root for the clients:

docker compose -f compose.single.yaml exec caddy cat /data/caddy/pki/authorities/local/root.crt > oxysdb-root.crt

and select oxysdb-root.crt as CA certificate file in the app, or see TLS for a real certificate.

HA on MongoDB

cd deploy/compose
./make-secrets.sh
docker compose -f compose.ha-mongodb.yaml up -d
printf '%s\n' 'a long passphrase' | \
  docker compose -f compose.ha-mongodb.yaml exec -T server-1 oxysdb-server add-account --email [email protected]
  • The file references the mongo:8.0 image, which Docker pulls on your machine (SSPL licence, not shipped by us). Set MONGO_IMAGE to use another build.
  • mongo-1..3 form replica set rs0 with key-file authentication. The one-shot mongo-init service initiates it and creates one user per product: oxysdb_server on database oxysdb_server only, oxysdb_runner on oxysdb_runner only. It runs again at every up (idempotent, and applies rotated passwords).
  • Team server on https://localhost:8443, runner on https://localhost:8444 (its token is in secrets/runner_token). Caddy checks /readyz every 3 s and retries a request on the other replica, so stopping one replica costs no failed request.

HA, fully open source (FerretDB)

cd deploy/compose
./make-secrets.sh
echo OXYSDB_ALLOW_SHARED_CREDENTIALS=true >> .env   # read the note below first
docker compose -f compose.ha-ferretdb.yaml up -d
printf '%s\n' 'a long passphrase' | \
  docker compose -f compose.ha-ferretdb.yaml exec -T server-1 oxysdb-server add-account --email [email protected]
  • FerretDB 2 (Apache-2.0) on PostgreSQL with the DocumentDB extension (PostgreSQL licence and MIT). FerretDB telemetry is off.
  • Shared credentials note: DocumentDB has no per-database roles. The server and the runners get separate users and databases, but each user could technically read and write the other’s database. The profile refuses to start until you accept this with OXYSDB_ALLOW_SHARED_CREDENTIALS=true. If that is not acceptable, use ha-mongodb or the Helm chart with a separate runner store.
  • FerretDB has no multi-document transactions: the server serializes its writes with a lock document. A replica killed in the middle of a request can leave that one request half applied; repeating it fixes it.
  • PostgreSQL is a single instance here: the profile survives the loss of a server, runner or FerretDB process, not of the PostgreSQL host. For database HA use the Helm chart with CloudNativePG, or a managed PostgreSQL that offers DocumentDB.

Pull-only runner

Both HA files have an optional runner-pull service, enabled with --profile pull-runner. It opens no port: it takes the queued runs of its pool (OXYSDB_PULL_RUNNER_POOL, default zone-a) from the shared database. In a real deployment it sits in the network zone of its target databases with egress to the shared database only.

docker compose -f compose.ha-mongodb.yaml --profile pull-runner up -d

Secrets

./make-secrets.sh [DIR] writes DIR (default ./secrets, mode 0700, ignored by git):

FileUsed for
mongo_keyfile, mongo_root_passwordMongoDB replica set
postgres_passwordPostgreSQL (FerretDB profile)
server_db_password, runner_db_passwordthe products’ database users
runner_master_passwordencrypts the runner’s saved connections: keep it safe
runner_tokenthe runner’s access token, for the desktop app
*_database_url.mongodb, *_database_url.ferretdb, ferretdb_postgresql_urlconnection URLs derived from the passwords

Existing files are kept; the URLs are rewritten each time. Secrets reach the containers as compose secrets: (/run/secrets/...) read through *_FILE variables: no password appears in the environment, on a command line or in docker inspect.

Rotate a database password: delete its file, run ./make-secrets.sh, then docker compose up -d (one replica at a time if you prefer: docker compose up -d --no-deps server-1, then server-2).

.env settings

Every value is optional; copy .env.example to .env.

VariableDefaultMeaning
OXYSDB_SERVER_IMAGE, OXYSDB_RUNNER_IMAGEoxysdb-server:latest, oxysdb-runner:latestimages to run
OXYSDB_SITE_ADDRESSlocalhosthost name clients use
OXYSDB_TLSinternalinternal (Caddy’s CA) or an e-mail address for ACME
OXYSDB_HTTPS_PUBLISH127.0.0.1:8443published team server port
OXYSDB_RUNNER_HTTPS_PUBLISH127.0.0.1:8444published runner port
OXYSDB_HTTP_PUBLISH127.0.0.1:8080HTTP port (ACME challenge)
OXYSDB_ALLOW_SIGN_UPfalselet anyone who reaches the URL create an account
OXYSDB_BACKEND_SUBNET, OXYSDB_BACKEND_IP_RANGE, OXYSDB_CADDY_IP, OXYSDB_TRUSTED_PROXIES10.250.250.0/24, 10.250.250.128/25, 10.250.250.2, Caddy’s IPthe backend network; only Caddy’s X-Forwarded-For is trusted
OXYSDB_LOG_FORMATtexttext or json
OXYSDB_SECRETS_DIR./secretsoutput of make-secrets.sh
OXYSDB_ALLOW_SHARED_CREDENTIALSfalseha-ferretdb only, see above
OXYSDB_PULL_RUNNER_POOLzone-apool of the optional pull-only runner
CADDY_IMAGE, MONGO_IMAGE, FERRETDB_IMAGE, POSTGRES_DOCUMENTDB_IMAGEpinned versionsthird-party images; pin by digest in production
FERRETDB_LOG_LEVELwarnFerretDB logging

Hardening built in

Every service runs with a read-only root filesystem, all capabilities dropped (MongoDB, PostgreSQL and Caddy add back only what their entrypoints need), no-new-privileges, a small tmpfs /tmp, memory and CPU limits, health checks and restart: unless-stopped. Ports are published on 127.0.0.1 only, and only Caddy publishes anything. The database network is internal (no route out); the runners also join a targets network to reach the databases their tasks use.

Move from single to an HA profile

Bring up the HA profile, then copy the SQLite store into it:

docker run --rm -v oxysdb-single_server-data:/data:ro --network oxysdb-ha-mongodb_db \
  -v "$PWD/secrets/server_database_url.mongodb":/run/secrets/url:ro \
  -e OXYSDB_SERVER_DATABASE_URL_FILE=/run/secrets/url -e OXYSDB_SERVER_DATABASE_NAME=oxysdb_server \
  oxysdb-server:dev migrate-to-mongodb --database /data/oxysdb-server.sqlite

Accounts keep their passwords and users stay signed in. The target must be empty unless --force.

Troubleshooting

SymptomCause and fix
ha-ferretdb: nothing starts, ferretdb-init exits with an explanationAccept the shared credentials (OXYSDB_ALLOW_SHARED_CREDENTIALS=true) or use another profile
Clients get a certificate errorWith OXYSDB_TLS=internal, trust Caddy’s root certificate (see above)
Unreachable from other machinesPorts are published on loopback by default: set OXYSDB_HTTPS_PUBLISH=443 (and the runner’s)
Sign-in throttling hits everyone at onceOXYSDB_TRUSTED_PROXIES does not match Caddy’s address: keep it equal to OXYSDB_CADDY_IP
A service is unhealthydocker compose -f FILE logs -f <service>

deploy/test/compose-smoke.sh <profile> brings a profile up under a throwaway project name, signs in through the proxy, creates a shared folder and (HA) kills a server replica to check that service continues.