Skip to content

The Docker Compose stack

docker compose up --build in the repo root is the entire self-hosting setup. It starts four services plus a one-shot init container:

Service Image Default port Purpose
mongo mongo:7 27017 Questionnaires, versions, responses, file index
minio minio/minio:latest 9000 (API), 9001 (console) S3-compatible file storage
minio-init minio/mc:latest — One-shot: creates the uploads bucket, then exits
backend built from ./backend 8080 Management + public runtime API
editor built from ./editor (nginx) 8081 The questionnaire editor SPA

Data lives in two named volumes, mongo-data and minio-data — it survives docker compose down (but not down -v).

Everything is environment-driven; put overrides in an .env file next to docker-compose.yml or export them in your shell.

Variable Default What it does
BACKEND_PORT 8080 Host port for the backend
EDITOR_PORT 8081 Host port for the editor
MONGO_PORT 27017 Host port for MongoDB
MINIO_PORT / MINIO_CONSOLE_PORT 9000 / 9001 Host ports for MinIO
MINIO_ROOT_USER / MINIO_ROOT_PASSWORD minioadmin / minioadmin MinIO credentials — change these for anything non-local; they’re also fed to the backend as its S3 keys
FE_API_BASE http://localhost:8080 The browser-facing URL of the backend, baked into the editor’s runtime config and its embed snippets. Change this whenever the stack is reachable from anywhere but localhost
STORAGE_MODE s3 (in the stack) Where uploaded files go — see below

The backend service also receives the full set of backend environment variables — storage, upload caps, geocoder, rate limits.

The shipped stack sets STORAGE_MODE=s3 pointed at the bundled MinIO, so you exercise the production-identical S3 upload path from day one, and the minio-init container creates the bucket automatically — no manual clicking.

For a minimal install without MinIO, set STORAGE_MODE=filesystem (and remove the minio / minio-init services). Files then go to a directory inside the backend container — add a volume for it or they vanish with the container. Details and trade-offs: File storage.

At startup the backend writes, reads back, and deletes a canary object and refuses to start if storage is unreachable or misconfigured, so storage problems surface at deploy time, not at your first respondent’s upload.

Three things to change:

  1. FE_API_BASE — set it to the public URL of the backend (e.g. https://forms.your-domain.com), or the editor and every embed snippet it generates will point browsers at localhost.
  2. Protect the management surface. The editor and /api/v1/** have no authentication. Put them behind a reverse proxy with auth, a VPN, or network policy — see Reverse proxy & exposure.
  3. Change the MinIO credentials if MinIO’s ports are reachable.
  • The image is a two-stage build (Maven → eclipse-temurin:21-jre); tests are skipped during the image build.
  • The container EXPOSEs 8080 regardless of the PORT variable — if you change PORT, publish that port explicitly.
  • There is no health-check endpoint (no Spring Actuator); the startup storage canary is the deploy-time check. If you need a liveness probe, a GET /v3/api-docs returning 200 is the closest thing available.
  • The process runs as root inside the container (no USER directive) — worth knowing if your platform enforces non-root images.