Deployment¶
EasyStudy is a Flask app. Configuration is entirely via environment variables (see .env.example).
Configuration¶
| Variable | Default | Notes |
|---|---|---|
SECRET_KEY |
random per boot | Set a fixed value in production (else sessions reset on restart). |
DATABASE_URL |
sqlite:///db.sqlite |
Use Postgres for real deployments, e.g. postgresql+psycopg://user:pass@host/db. |
SESSION_TYPE |
sqlalchemy |
redis (+ REDIS_URL) for a shared, faster session store. |
REDIS_URL |
unset | Only needed if SESSION_TYPE=redis. |
PORT |
8000 (Docker) |
Host-side port for docker compose up; the container always listens on 8000 internally. |
GUNICORN_WORKERS |
1 |
Keep at 1 unless you've done the shared-state rework (see note). |
GUNICORN_TIMEOUT |
0 |
Gunicorn worker timeout (seconds). |
EASYSTUDY_EXTRAS |
empty | Build-time: heavy extras baked into the Docker image (recbole, tensorflow, lenskit, …). |
PYTHON_VERSION |
3.12 |
Build-time: the recbole extra needs an older wheel-only dependency, so build it on 3.11. |
Docker (recommended)¶
docker compose run --rm fetch # first run: fetch datasets
docker compose up --build # http://localhost:8000
- Include heavy backends:
EASYSTUDY_EXTRAS="recbole" docker compose up --build(needsPYTHON_VERSION=3.11too), orEASYSTUDY_EXTRAS="tensorflow,lenskit". - Redis sessions:
docker compose --profile redis upand setSESSION_TYPE=redis,REDIS_URL=redis://redis:6379.
Put a reverse proxy (nginx/Caddy/Traefik) in front for TLS.
From source (uWSGI/Gunicorn)¶
The easystudy CLI wraps this for you: easystudy serve --prod (see the
Quickstart) runs the same gunicorn invocation without needing to cd server
or hand-write the command. Equivalent, if you'd rather run it directly:
uv sync --extra tensorflow # or just `uv sync` for the lightweight core
cd server
uv run gunicorn -w 1 --bind 0.0.0.0:8000 "app:create_app()"
Concurrency & the single worker
Today the per-participant fine-tuned model state is kept in worker memory. Running multiple
Gunicorn workers can therefore send a participant's requests to a worker that doesn't have their
state. Until the shared-state rework lands (moving fine-tuning to a task queue / storing per-user
deltas — see the technical backlog), keep GUNICORN_WORKERS=1 and scale by running independent
instances behind a load balancer with sticky sessions, or by mirroring studies across servers.
Database migrations¶
Schema changes ship as Flask-Migrate migrations:
cd server
flask db upgrade # apply
flask db migrate -m "…" # create a new migration after changing models.py
Backups¶
Back up the database (server/instance/db.sqlite or your Postgres) regularly during a live study — it
holds all collected data.