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).
The knobs
Section titled “The knobs”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.
Storage: the stack default is MinIO
Section titled “Storage: the stack default is MinIO”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.
Exposing the stack beyond localhost
Section titled “Exposing the stack beyond localhost”Three things to change:
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 atlocalhost.- 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. - Change the MinIO credentials if MinIO’s ports are reachable.
Worth knowing about the backend image
Section titled “Worth knowing about the backend image”- 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 thePORTvariable — if you changePORT, 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-docsreturning 200 is the closest thing available. - The process runs as root inside the container (no
USERdirective) — worth knowing if your platform enforces non-root images.