This guide explains how to build, run, and deploy the OpenML Upload application using Docker.
-- Docker installed
From the repository root:
docker build -f backend/Dockerfile -t openml-upload .The backend Dockerfile at backend/Dockerfile uses a multi-stage build that compiles the React frontend, installs Python dependencies, and produces a slim production image. No extra build arguments are required.
The easiest way to run a complete instance is the root Compose stack. It starts the application, Postgres, ClamAV, and Caddy:
cp .env.example .env
docker compose up -d --buildWith the default .env values, the application is available at http://localhost:8000.
If you are using the repository's encrypted SOPS secrets, do not run plain docker compose up -d --build. Start or rebuild the stack with the decrypted environment injected into Compose:
sops exec-env encrypted.env 'docker compose -f compose.yml up -d --build'Use that exact command for local Compose runs that depend on encrypted values such as GITHUB_CLIENT_ID, GITHUB_SECRET, JWT_SECRET, or GitHub App credentials. Otherwise Compose falls back to the defaults in compose.yml, and GitHub login will not be configured.
For GitHub login, create a GitHub OAuth App and set these deployment-time credentials in the root .env file:
GITHUB_CLIENT_ID=your-client-id
GITHUB_SECRET=your-client-secret
GITHUB_OAUTH_SCOPES=read:user,user:email,read:orgThe callback URL configured in the GitHub OAuth App must point to the frontend callback route, for example http://localhost:8000/login/callback for the local Compose stack. End users do not provide these values when they log in; they identify this deployed application to GitHub.
The GitHub App settings below let the app create review issues and verify repository collaborator permissions for expert-role assignment without requesting broad user repository access. Install the GitHub App on the issue repository, and also on the permission-check repository if that is different.
There are two GitHub repository targets:
GITHUB_ISSUES_OWNERandGITHUB_ISSUES_REPOdecide where dataset review issues are created.GITHUB_PERMISSION_OWNERandGITHUB_PERMISSION_REPOdecide which repository is checked when assigning the app'sexpertrole. GitHub users withmaintainoradminpermission on that repository become experts in the app.
When GITHUB_PERMISSION_OWNER or GITHUB_PERMISSION_REPO is unset, it defaults to the matching issue repository value. That means a simple deployment only needs the issue variables. Set the permission variables only when expert-role checks should use a different repository from issue creation.
Dataset review issue creation defaults to koevoet1221/openmlupload-testing for this test deployment. The GitHub App credentials must also be present for issue creation and permission checks to run:
GH_APP_ID=your-github-app-id
GH_INSTALL_ID=your-github-app-installation-id
GH_PRIV_KEY=your-github-app-private-key
GITHUB_ISSUES_OWNER=koevoet1221
GITHUB_ISSUES_REPO=openmlupload-testing
# Optional only when role checks should use a different repository:
# GITHUB_PERMISSION_OWNER=your-permission-owner
# GITHUB_PERMISSION_REPO=your-permission-repoFor the official deployment, update GITHUB_ISSUES_OWNER and GITHUB_ISSUES_REPO in .env. If expert-role checks should use a different repository than issue creation, also set GITHUB_PERMISSION_OWNER and GITHUB_PERMISSION_REPO.
For a production domain, use the HTTPS callback, for example:
APP_BASE_URL=https://upload.example.com
COOKIE_SECURE=true
CADDY_SITE_ADDRESS=upload.example.com
HTTP_PORT=80
HTTPS_PORT=443
GH_APP_ID=official-github-app-id
GH_INSTALL_ID=official-github-app-installation-id
GH_PRIV_KEY=official-github-app-private-key
GITHUB_ISSUES_OWNER=official-owner
GITHUB_ISSUES_REPO=official-repo
# Optional only when role checks should use a different repository:
# GITHUB_PERMISSION_OWNER=official-permission-owner
# GITHUB_PERMISSION_REPO=official-permission-repoFor a local smoke test without GitHub OAuth, set:
AUTH_DEV_MODE_APPROVE_ALL_LOGINS=truedocker run -d \
-p 8000:8000 \
-v openml-data:/data \
--name openml-upload \
openml-upload| Flag | Purpose |
|---|---|
-p 8000:8000 |
Maps the container's application port to your host. Change the first number to use a different host port (e.g.-p 3000:8000). |
-v openml-data:/data |
Mounts a named Docker volume to/data inside the container, where the application persists uploaded files. |
-d |
Runs the container in the background. |
Once running, the application is available at http://localhost:8000.
This starts the application container only. Dataset upload confirmation also requires a reachable ClamAV clamd daemon; see Upload malware scanning.
The application reads the following environment variables. Pass them with -e flags:
docker run -d \
-p 8000:8000 \
-v openml-data:/data \
-e STORAGE_BACKEND=local \
-e LOCAL_UPLOAD_DIR=/data/uploads \
-e QUARANTINE_DIR=/data/quarantine \
-e CLAMD_HOST=clamav \
-e CLAMD_PORT=3310 \
--name openml-upload \
openml-upload| Variable | Default | Description |
|---|---|---|
STORAGE_BACKEND |
local |
Storage backend to use for uploads. |
LOCAL_UPLOAD_DIR |
.local_uploads |
Directory for locally stored uploads. Set this to a path under/data (e.g. /data/uploads) so that uploads are persisted on the mounted volume. |
QUARANTINE_DIR |
.quarantine |
Directory used for temporary scan copies before promotion. |
CLAMD_SOCKET |
Unix socket path forclamd. When set, this takes precedence over CLAMD_HOST and CLAMD_PORT. |
|
CLAMD_HOST |
127.0.0.1 |
Hostname forclamd when using TCP. |
CLAMD_PORT |
3310 |
TCP port forclamd. |
CLAMD_TIMEOUT_SECONDS |
60 |
Timeout for ClamAV daemon calls. |
S3_BUCKET |
Required when STORAGE_BACKEND=s3; bucket for quarantined and promoted dataset objects. |
|
S3_REGION |
Region passed to the S3 client. | |
S3_ENDPOINT |
Custom endpoint for MinIO or another S3-compatible service. Leave empty for AWS S3. | |
S3_PUBLIC_ENDPOINT |
Optional endpoint used only for presigned browser URLs when S3_ENDPOINT is not reachable from the browser. |
|
S3_ACCESS_KEY |
Static access key for local or static S3 credentials. | |
S3_SECRET_KEY |
Static secret key for local or static S3 credentials. | |
S3_FORCE_PATH_STYLE |
false |
Enables path-style bucket addressing for MinIO and similar S3-compatible services. |
Uploaded datasets are scanned before they are promoted from quarantine storage to the ready/downloadable location. Production and production-like deployments must run a ClamAV clamd daemon as a sidecar, sibling container, host service, or managed service and configure the application with CLAMD_SOCKET or CLAMD_HOST/CLAMD_PORT.
When clamd is reached over TCP, the daemon must be able to read the configured QUARANTINE_DIR at the same path as the application. The production Compose stack mounts openml-data into the clamd container read-only for this reason.
If clamd is unavailable, uploaded bytes are not modified, but the scan records an error, the dataset is marked quarantined, and the uploaded objects are not promoted for download or expert review.
For local upload-flow development, prefer the development Compose stack because it starts clamd with the backend:
docker compose -f docker-compose.dev.yml up backend frontend minio minio-init clamdA pre-built image is published to GitHub Container Registry on every push to the default branch.
Because the repository is private, you need to authenticate with GHCR before pulling. Create a personal access token (classic) with the read:packages scope, then log in:
echo YOUR_GITHUB_PAT | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdinOnce authenticated, pull and run the image:
docker pull ghcr.io/ludev-nl/2026-40-openml_uploading_interface:latest
docker run -d \
-p 8000:8000 \
-v openml-data:/data \
-e LOCAL_UPLOAD_DIR=/data/uploads \
--name openml-upload \
ghcr.io/ludev-nl/2026-40-openml_uploading_interface:latestSee docker-build.md for details on the CI workflow and tagging scheme.
docker container rm openml-upload
docker image rm openml-uploadYour data is safe in the openml-data volume and will be reused the next time you start a container with the same volume.
Reverse proxy. In production, place the container behind a reverse proxy (e.g. Nginx, Caddy, or Traefik) that handles TLS termination, rate limiting, and serving on port 443.
Restart policy. Add --restart unless-stopped to the docker run command so the container restarts automatically after a host reboot or crash.
Resource limits. You can constrain CPU and memory usage:
docker run -d \
-p 8000:8000 \
-v openml-data:/data \
--restart unless-stopped \
--memory 512m \
--cpus 1 \
openml-uploadSecurity. The image already runs as a non-root user (appuser). Avoid running the container with --privileged or overriding the user.
Logging. View container logs with:
docker logs -f openml-upload