# ClassQuiz and the case for a self-hosted classroom quiz

> ClassQuiz is an open source quiz platform in the Kahoot mould, built as a SvelteKit frontend over a FastAPI backend with socket.io for live play. It is aimed at teachers and schools that want to run interactive quizzes on their own infrastructure rather than on a vendor's.

**mawoka-myblock/ClassQuiz** — ClassQuiz is a quiz-application like Kahoot!, but open-source.

- Repository: https://github.com/mawoka-myblock/ClassQuiz
- Website: https://classquiz.de
- Stars: 733 · Forks: 183
- Language: Svelte
- License: MPL-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/mawoka-myblock-classquiz

## ClassQuiz exists because a quiz tool for schools should not need a vendor

The repository description calls ClassQuiz a quiz application like Kahoot, but open-source, and the README makes the reasoning explicit. It is a quiz app to learn interactively for students, and open source matters for it because it is a product for educational purposes. Teachers create a quiz and students play it remotely, competing against each other on knowledge.

That framing sets the audience precisely. This is not a consumer game, and it is not a general survey tool. The design assumption is a teacher standing in front of a class with a shared screen, and a room of students joining with short codes, answering questions at speed.

The project runs a hosted instance at classquiz.de, with the server located in Karlsruhe, Germany and hosted by netcup, and the README warns to expect some latency depending on location. That hosted service is also the demo, and it is a fair reason to look before self-hosting.

## A SvelteKit frontend over a FastAPI backend, connected by socket.io

The stack is described in the README's tech-stack section. The backend is FastAPI as the web framework, ormar as the ORM, and python-socketio for realtime communication between server and client. The frontend is SvelteKit as the web framework with TailwindCSS for styling.

The socket.io choice is the interesting part. A live quiz is a state machine that both the host screen and every player screen have to agree on, and the answer key, the timer and the scoreboard all have to stay consistent. Carrying that over a websocket rather than polling is the normal choice, and python-socketio on the server with the JavaScript client is the usual pairing.

The repository is a monorepo. The frontend lives in the `frontend/` directory, while the backend project, which uses a Pipfile, sits in the root with its code in the `classquiz/` folder. The root also carries `migrations/` and `alembic.ini` for database migrations, `Caddyfile` and `Caddyfile-docker` for the reverse proxy, and `SocketIo.md` as a note on the socket layer.

## Self-hosting means running Postgres, Redis, Meilisearch and Caddy

The README lists the external dependencies in two groups. The self-hostable ones are Meilisearch as the search server, Caddy as the reverse proxy, Postgres as the database and Redis as the cache. The closed-source third parties are Mapbox for maps and hCaptcha for captcha.

That is a substantial stack for what is conceptually a form and a scoreboard. Four services to operate, plus a schema migration story with Alembic, plus a websocket-aware reverse proxy in front. Anyone evaluating this needs to be clear that self-hosting ClassQuiz is closer to deploying a small platform than to running a single container.

The docker-compose.yml shows how the pieces are wired. The frontend service is built from the `./frontend` context and depends on redis and api, and it receives `REDIS_URL` and `API_URL` in its environment:

```yaml
  frontend:
    restart: always
    build:
      context: ./frontend
      dockerfile: Dockerfile
    depends_on:
      - redis
      - api
    environment:
      REDIS_URL: redis://redis:6379/0?decode_responses=True
      API_URL: http://api:80
```

The api service runs the published image `ghcr.io/mawoka-myblock/classquiz-backend:master` and depends on the db and redis services.

## The container builds its requirements out of Pipfile.lock at image build time

The backend image does not install from a plain requirements file that a developer maintains by hand. Instead the Dockerfile derives one from the lock file:

```dockerfile
FROM python:3.13-slim

COPY Pipfile* /app/
WORKDIR /app/
RUN apt update && \
    apt install -y jq gcc libpq5 libpq-dev libmagic1 && \
    jq -r '.default | to_entries[] | .key + .value.version' Pipfile.lock > requirements.txt && \
    sed -i "s/psycopg2-binary/psycopg2/g" requirements.txt
```

The `jq` expression flattens the `default` section of `Pipfile.lock` into a pip compatible list, and the `sed` step swaps the binary psycopg2 wheel for the source build. The apt packages `libpq5` and `libpq-dev` are there for that build, and `libmagic1` for file type detection. Both `jq` and `gcc` are removed again in the next layer, so the runtime image does not carry the toolchain.

The rest of the file copies the application code, the migrations, the startup scripts and `gunicorn_conf.py`, exposes port 80, sets `PYTHONPATH=/app` and `APP_MODULE=classquiz:app`, and runs `./start.sh`. The image is based on `python:3.13-slim`, while the Python target configured for the formatter in `pyproject.toml` is `py310`:

```toml
[tool.black]
line-length = 120
target-version = ["py310"]
```

## Configuration is environment variables, and one of them ships as TOP_SECRET

The api service in `docker-compose.yml` carries the application configuration as environment variables, and the file marks a block of them as values that should not be changed, with a comment saying to use the one-liner provided in the documentation.

Among them are `SECRET_KEY`, whose shipped value is the literal string `TOP_SECRET`, `DB_URL` pointing at the Postgres service with a `postgres:classquiz` credential pair, `REDIS`, `MAX_WORKERS` set to 1 and marked as very important, `ACCESS_TOKEN_EXPIRE_MINUTES` set to 30, and `MEILISEARCH_URL`. The block is closed with a comment that ends the protected region.

Below that are the values a self-hoster must change. `ROOT_ADDRESS` defaults to `https://classquiz.de` and is marked CHANGE IT, without a trailing slash. The mail section sets `MAIL_PORT` to 587, placeholders for address, username, password and server, and `SKIP_EMAIL_VERIFICATION` set to `True`, which answers the question of whether a user has to confirm their email by clicking a link. The storage section sets `STORAGE_BACKEND` to `local`, with `STORAGE_PATH` at `/app/data`, and a comment notes that s3 is also possible.

Two things deserve attention before deployment. A `SECRET_KEY` left as `TOP_SECRET` in a reachable deployment is a problem, and `SKIP_EMAIL_VERIFICATION` set to `True` means registration does not confirm an email address.

## Maps and captcha still point at closed-source services

Self-hostable does not mean fully self-contained here. The README separates the dependencies into self-hostable ones and closed-source third parties, and the latter are Mapbox for maps and hCaptcha for captcha. The compose file also carries commented-out keys for `HCAPTCHA_KEY`, `PIXABAY_API_KEY` and `RECAPTCHA_KEY`, which shows the captcha and image provider integrations are optional and switched off by default.

So a deployment that enables captcha needs an hCaptcha account, and any map feature needs Mapbox. For an educational tool this is usually a reasonable trade, since the core quiz loop has no such dependency, but for a school with a strict data processing policy the distinction between self-hostable services and external accounts is the thing to check.

The upstream service, Mapbox and hCaptcha, are separate companies with separate terms. The README does not describe how the application behaves when those keys are absent, so the absence path is worth exercising in a test deployment.

## MPL 2.0, Weblate translations, and release tags that stopped in 2022

The repository is licensed under the Mozilla Public License 2.0, and the README adds a note with a pointed explanation: the note was added because people were taking the software, changing it and not providing the source code. The SPDX headers throughout the files carry the same identifier and attribute 2023 Marlon W (Mawoka). There is a `LICENSES/` directory and a `.reuse/` directory, which is the REUSE licensing setup.

Translations are handled through hosted Weblate, with the widget in the README showing translation status for the frontend.

The maintenance picture is mixed and worth stating plainly. The repository is not archived and the last push was on 2026-10-12, so work is happening. But the most recent tagged releases are v0.1.3 from 2022-05-11, v0.1.2 from 2022-05-07 and v0.1.1 from 2022-04-30. A four year gap between the newest tag and the newest commit suggests that release tagging has stopped even though development continues, so the tags are a poor guide to the current state of the code and the compose file pins the backend image to the `master` tag rather than a version.

The repository also carries the tooling of an active project: `run_tests.sh`, `pytest.ini`, a `.pre-commit-config.yaml`, `.flake8`, a `.deepsource.toml`, and `load-test.py` and `simulate_players.py` for exercising the live game.

## Conclusion

ClassQuiz is a reasonable choice for a school or training organisation that wants a Kahoot-style live quiz on infrastructure it controls, and that can accept running Postgres, Redis, Meilisearch and Caddy alongside the application. It is a poor fit for a classroom with no Docker experience, for an organisation that needs maps or captcha without third-party accounts, and for anyone expecting an actively versioned release stream. Verify first the state of the `docker-compose.yml` secrets in your deployment, since the shipped `SECRET_KEY` value is the literal string `TOP_SECRET`, and set `ROOT_ADDRESS`, the mail settings and `SKIP_EMAIL_VERIFICATION` to match your own domain before you register users.

## FAQ

### How do I self-host ClassQuiz?

The README points to the self-hosting documentation at https://classquiz.de/docs/self-host. The repository ships a `docker-compose.yml`, a `Dockerfile` for the backend, `Caddyfile` and `Caddyfile-docker` for the reverse proxy, and a frontend built from the `./frontend` context.

### What services does a self-hosted ClassQuiz need?

The README lists Meilisearch as the search server, Caddy as the reverse proxy, Postgres as the database and Redis as the cache. The compose file wires a frontend service to redis and api, and the api service depends on db and redis.

### Which configuration values must change before deploying ClassQuiz?

The compose file marks `ROOT_ADDRESS` as CHANGE IT and ships `SECRET_KEY` as `TOP_SECRET`, with a note to use the one-liner from the documentation instead. Mail settings such as `MAIL_SERVER` and `MAIL_USERNAME` also carry placeholder values, and `SKIP_EMAIL_VERIFICATION` is set to `True`.

### Does ClassQuiz depend on Mapbox or hCaptcha?

The README separates the self-hostable dependencies from closed-source third parties, naming Mapbox for maps and hCaptcha for captcha. The compose file lists `HCAPTCHA_KEY`, `PIXABAY_API_KEY` and `RECAPTCHA_KEY` as commented-out entries, so those integrations are optional.

### What licence is ClassQuiz released under?

The repository is licensed under the Mozilla Public License 2.0, with the terms in the LICENSE file and a `LICENSES/` directory. The README asks readers to review the licence to understand their rights and obligations.

## Sources

- [License: MPL-2.0](https://github.com/mawoka-myblock/ClassQuiz/blob/master/LICENSE)
- [mawoka-myblock/ClassQuiz on GitHub](https://github.com/mawoka-myblock/ClassQuiz)
- [Project website](https://classquiz.de)
- [README](https://github.com/mawoka-myblock/ClassQuiz/blob/master/README.md)
- [Releases](https://github.com/mawoka-myblock/ClassQuiz/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mawoka-myblock-classquiz
