Docker
The production stack is defined in docker-compose.yaml. The Caddy container is the entry point to the core platform: it publishes APP_PORT on the host and forwards API requests (/api/*) to the api container, file requests (/storage/*) to the rustfs container while file storage is enabled, and every other request to the web container, which serves the single-page app. The gateway publishes its own port, GATEWAY_PORT.
Services
| Service | Purpose | Required |
|---|---|---|
caddy | Entry point that routes requests to the other services | Yes |
api | REST API | Yes |
web | The web app clinicians and administrators use | Yes |
mongo | Database | Yes |
gateway | Serves remote assignments to participants | Only to send remote assignments |
rustfs | S3-compatible object storage | Only for file instruments |
playground | Instrument playground | No, and it is not started by default |
Keep caddy even if you already run a reverse proxy. The web app calls the API at the relative path /api, and neither the api nor the web container publishes a port, so point your own proxy at APP_PORT instead, as in the deployment tutorial.
Profiles
Every service belongs to one or more Compose profiles. Without a profile, docker compose up -d starts every service except the playground.
| Profile | Services started |
|---|---|
| (none) | caddy, api, web, mongo, rustfs, gateway |
nogateway | caddy, api, web, mongo, rustfs |
gateway | caddy, gateway |
fullstack | all seven services |
playground | playground |
To use a profile without passing --profile to every command, set COMPOSE_PROFILES in .env. Docker Compose reads it from there.
Minimal stacks
Leave docker-compose.yaml unchanged, so that updating Open Data Capture with git pull never conflicts with your changes. Choose services with a profile, and put any other change in a docker-compose.override.yaml next to it, which Docker Compose merges into docker-compose.yaml automatically.
Without the gateway
Without the gateway, the instance cannot send remote assignments. Set the following in .env:
COMPOSE_PROFILES=nogatewayGATEWAY_ENABLED=falseGATEWAY_ENABLED=false turns remote assignments off in the API and hides them in the web app. Leave GATEWAY_API_KEY and GATEWAY_SITE_ADDRESS as generated, because the API still checks them when it starts.
Without file storage
Without object storage, file instruments are unavailable. Set the following in .env:
STORAGE_ENABLED=falseThe other STORAGE_* variables are then ignored. Docker Compose would still start rustfs, though: it belongs to every profile that starts the api, and the api lists it as a dependency. Create docker-compose.override.yaml next to docker-compose.yaml:
services: api: depends_on: !override - mongo rustfs: profiles: !override - storageThis removes rustfs from the api’s dependencies and moves it into a storage profile that nothing activates. The !override tag requires Docker Compose 2.24.4 or later.
The smallest stack
Combine the two: the .env settings from both sections above, plus the override file. The stack then runs only caddy, api, web and mongo.
To check which services will start before launching, run:
docker compose config --servicesIf a service you have just left out is still running from before, stop and remove its container, for example:
docker compose rm --stop gateway