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.
| Profile | File | What runs |
|---|---|---|
single | compose.single.yaml | one team server on SQLite (named volume) + Caddy |
ha-mongodb | compose.ha-mongodb.yaml | a 3-member MongoDB replica set, 2 team servers, 2 runners (database mode), Caddy load-balancing both |
ha-ferretdb | compose.ha-ferretdb.yaml | PostgreSQL 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
# First account; the password is the first line of standard input (at least 8 characters)
| \
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:
and select oxysdb-root.crt as CA certificate file in the app, or see TLS
for a real certificate.
HA on MongoDB
| \
- The file references the
mongo:8.0image, which Docker pulls on your machine (SSPL licence, not shipped by us). SetMONGO_IMAGEto use another build. mongo-1..3form replica setrs0with key-file authentication. The one-shotmongo-initservice initiates it and creates one user per product:oxysdb_serveron databaseoxysdb_serveronly,oxysdb_runneronoxysdb_runneronly. It runs again at everyup(idempotent, and applies rotated passwords).- Team server on
https://localhost:8443, runner onhttps://localhost:8444(its token is insecrets/runner_token). Caddy checks/readyzevery 3 s and retries a request on the other replica, so stopping one replica costs no failed request.
HA, fully open source (FerretDB)
| \
- 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, useha-mongodbor 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.
Secrets
./make-secrets.sh [DIR] writes DIR (default ./secrets, mode 0700, ignored by git):
| File | Used for |
|---|---|
mongo_keyfile, mongo_root_password | MongoDB replica set |
postgres_password | PostgreSQL (FerretDB profile) |
server_db_password, runner_db_password | the products’ database users |
runner_master_password | encrypts the runner’s saved connections: keep it safe |
runner_token | the runner’s access token, for the desktop app |
*_database_url.mongodb, *_database_url.ferretdb, ferretdb_postgresql_url | connection 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.
| Variable | Default | Meaning |
|---|---|---|
OXYSDB_SERVER_IMAGE, OXYSDB_RUNNER_IMAGE | oxysdb-server:latest, oxysdb-runner:latest | images to run |
OXYSDB_SITE_ADDRESS | localhost | host name clients use |
OXYSDB_TLS | internal | internal (Caddy’s CA) or an e-mail address for ACME |
OXYSDB_HTTPS_PUBLISH | 127.0.0.1:8443 | published team server port |
OXYSDB_RUNNER_HTTPS_PUBLISH | 127.0.0.1:8444 | published runner port |
OXYSDB_HTTP_PUBLISH | 127.0.0.1:8080 | HTTP port (ACME challenge) |
OXYSDB_ALLOW_SIGN_UP | false | let anyone who reaches the URL create an account |
OXYSDB_BACKEND_SUBNET, OXYSDB_BACKEND_IP_RANGE, OXYSDB_CADDY_IP, OXYSDB_TRUSTED_PROXIES | 10.250.250.0/24, 10.250.250.128/25, 10.250.250.2, Caddy’s IP | the backend network; only Caddy’s X-Forwarded-For is trusted |
OXYSDB_LOG_FORMAT | text | text or json |
OXYSDB_SECRETS_DIR | ./secrets | output of make-secrets.sh |
OXYSDB_ALLOW_SHARED_CREDENTIALS | false | ha-ferretdb only, see above |
OXYSDB_PULL_RUNNER_POOL | zone-a | pool of the optional pull-only runner |
CADDY_IMAGE, MONGO_IMAGE, FERRETDB_IMAGE, POSTGRES_DOCUMENTDB_IMAGE | pinned versions | third-party images; pin by digest in production |
FERRETDB_LOG_LEVEL | warn | FerretDB 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:
Accounts keep their passwords and users stay signed in. The target must be empty unless --force.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
ha-ferretdb: nothing starts, ferretdb-init exits with an explanation | Accept the shared credentials (OXYSDB_ALLOW_SHARED_CREDENTIALS=true) or use another profile |
| Clients get a certificate error | With OXYSDB_TLS=internal, trust Caddy’s root certificate (see above) |
| Unreachable from other machines | Ports are published on loopback by default: set OXYSDB_HTTPS_PUBLISH=443 (and the runner’s) |
| Sign-in throttling hits everyone at once | OXYSDB_TRUSTED_PROXIES does not match Caddy’s address: keep it equal to OXYSDB_CADDY_IP |
| A service is unhealthy | docker 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.