Skip to main content

Overview

ClassQuiz is fully open-source under the Mozilla Public License 2.0, enabling you to host your own instance with complete control over your data and customization.
License Compliance: The MPL 2.0 license requires you to publish any modifications you make to the source code. This is a strict requirement, not optional.
While official releases are available, the maintainer recommends running the latest commit from the master branch where CI checks pass, as it contains the most recent bug fixes and improvements.

Prerequisites

Required Software

Docker & Docker Compose

Container orchestration for all services

Git

Version control to clone the repository

OpenSSL

For generating secure secret keys

SSL Certificate

HTTPS is required for proper WebSocket functionality

System Requirements

  • CPU: 2+ cores recommended
  • RAM: 4GB minimum, 8GB+ recommended for production
  • Storage: 10GB+ depending on media uploads
  • Network: Public IP address or domain name with SSL

Optional Third-Party Services

Required for user registration and email verification.
  • Configure any SMTP server (Gmail, SendGrid, Mailgun, etc.)
  • Default: Email verification can be disabled
Enable single sign-on for users:
Prevent spam registrations:
Monitor application errors:

Installation

1

Clone the Repository

git clone https://github.com/mawoka-myblock/ClassQuiz.git
cd ClassQuiz
For production deployments, consider using a specific commit or tag for stability:
git checkout <commit-hash>
2

Configure Frontend Build

Edit frontend/Dockerfile to set build-time environment variables:
# Captcha Configuration
ENV VITE_HCAPTCHA=your_hcaptcha_sitekey_here
ENV VITE_CAPTCHA_ENABLED=true

# OAuth Configuration
ENV VITE_GOOGLE_AUTH_ENABLED=true
ENV VITE_GITHUB_AUTH_ENABLED=true
ENV VITE_CUSTOM_OAUTH_NAME="School SSO"

# Optional: Error Tracking
ENV VITE_SENTRY=your_sentry_dsn_here
These are build-time variables. Changes require rebuilding the frontend container:
docker compose build frontend

Frontend Environment Variables

VariableRequiredDescription
VITE_HCAPTCHANohCaptcha site key for captcha challenges
VITE_CAPTCHA_ENABLEDNoSet to true to enable captcha on registration
VITE_GOOGLE_AUTH_ENABLEDNoSet to true to show Google login button
VITE_GITHUB_AUTH_ENABLEDNoSet to true to show GitHub login button
VITE_CUSTOM_OAUTH_NAMENoDisplay name for custom OpenID provider
VITE_SENTRYNoSentry DSN for frontend error tracking
3

Configure Backend Services

Edit the docker-compose.yml file to configure the API and worker services.

Essential Configuration

environment:
  # ===== REQUIRED: Change These Values =====
  
  # Your public domain (no trailing slash)
  ROOT_ADDRESS: "https://classquiz.yourdomain.com"
  
  # Generate with: openssl rand -hex 32
  SECRET_KEY: "TOP_SECRET"  # CHANGE THIS!
  
  # ===== Email Configuration =====
  MAIL_SERVER: "smtp.gmail.com"
  MAIL_PORT: "587"
  MAIL_USERNAME: "[email protected]"
  MAIL_PASSWORD: "your-app-password"
  MAIL_ADDRESS: "[email protected]"
  SKIP_EMAIL_VERIFICATION: "False"  # Set to "True" to disable email verification
  
  # ===== Storage Backend =====
  STORAGE_BACKEND: "local"  # Options: "local" or "s3"
  STORAGE_PATH: "/app/data"  # Required if STORAGE_BACKEND=local

Generate Secure Secret Key

Run this command to automatically generate and set a secure secret:
sed -i "s/TOP_SECRET/$(openssl rand -hex 32)/g" docker-compose.yml
Never use the default TOP_SECRET value in production! This would allow attackers to forge authentication tokens.
4

Configure Storage Backend

ClassQuiz requires a storage backend for uploaded media files.
Recommended for: Small deployments, single-server setups
docker-compose.yml
environment:
  STORAGE_BACKEND: "local"
  STORAGE_PATH: "/app/data"

volumes:
  - ./uploads:/app/data  # Persist uploads on host
Local storage stores files directly on the server filesystem. Ensure the ./uploads directory has appropriate permissions.
5

Configure OAuth (Optional)

Enable third-party authentication for easier user registration.
  1. Visit Google Cloud Console
  2. Create a new project or select existing
  3. Navigate to APIs & Services > OAuth consent screen
  4. Configure consent screen with your application details
  5. Go to Credentials > Create Credentials > OAuth Client ID
  6. Set application type to Web application
  7. Add Authorized JavaScript origins:
    https://classquiz.yourdomain.com
    
  8. Add Authorized redirect URIs:
    https://classquiz.yourdomain.com/api/v1/users/oauth/google/auth
    
  9. Copy the Client ID and Client Secret
Add to docker-compose.yml:
environment:
  GOOGLE_CLIENT_ID: "your-client-id.apps.googleusercontent.com"
  GOOGLE_CLIENT_SECRET: "your-client-secret"
Update frontend/Dockerfile:
ENV VITE_GOOGLE_AUTH_ENABLED=true
6

Configure Advanced Options

Captcha Protection

environment:
  # Choose ONE captcha provider
  HCAPTCHA_KEY: "your-hcaptcha-secret"     # hCaptcha (recommended)
  # OR
  RECAPTCHA_KEY: "your-recaptcha-secret"   # Google reCAPTCHA
Remember to also set the site key in frontend/Dockerfile.Enable Pixabay integration for quiz images:
environment:
  PIXABAY_API_KEY: "your-pixabay-api-key"
Get your key at pixabay.com/api/docs

Error Tracking

environment:
  SENTRY_DSN: "https://[email protected]/project-id"

Storage Limits

environment:
  FREE_STORAGE_LIMIT: "1074000000"  # 1GB in bytes

Admin and Moderation

environment:
  MODS: '["[email protected]", "[email protected]"]'  # JSON array of moderator emails
  REGISTRATION_DISABLED: "false"  # Set to "true" to disable new registrations

Token Expiration

environment:
  ACCESS_TOKEN_EXPIRE_MINUTES: 30  # Default: 30 minutes
  CACHE_EXPIRY: 86400              # Default: 24 hours (in seconds)
7

Build and Deploy

Build Containers

docker compose build
This builds:
  • frontend: SvelteKit frontend (Node.js)
  • api: FastAPI backend (Python)
  • worker: ARQ background worker (Python)

Start Services

docker compose up -d
This starts all services:
  • frontend (port 3000)
  • api (port 80)
  • worker
  • db (PostgreSQL)
  • redis (cache and queue)
  • meilisearch (search engine)
  • proxy (Caddy reverse proxy on port 8000)

Verify Deployment

# Check all containers are running
docker compose ps

# View logs
docker compose logs -f

# Check specific service
docker compose logs -f api
The Caddy proxy listens on port 8000 by default. Change this in docker-compose.yml under the proxy service:
ports:
  - "8080:8080"  # Change 8000 to your preferred port
8

Configure Reverse Proxy & SSL

ClassQuiz requires HTTPS for proper WebSocket and OAuth functionality.
/etc/nginx/sites-available/classquiz
server {
    listen 80;
    server_name classquiz.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name classquiz.yourdomain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # WebSocket support
    location /socket.io/ {
        proxy_pass http://localhost:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # API endpoints
    location /api/ {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Frontend
    location / {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Architecture Overview

Services

Tech Stack

Frontend

  • SvelteKit: Modern web framework
  • TailwindCSS: Utility-first CSS
  • Socket.IO: Real-time communication

Backend

  • FastAPI: High-performance Python API
  • Ormar: Async ORM
  • ARQ: Background task queue

Database

  • PostgreSQL: Primary database
  • Redis: Caching and queues
  • Meilisearch: Full-text search

Infrastructure

  • Docker: Containerization
  • Caddy: Reverse proxy
  • S3: Optional object storage

Environment Variables Reference

Database & Cache (Do Not Change)

VariableDefaultDescription
DB_URLpostgresql://postgres:classquiz@db:5432/classquizPostgreSQL connection string
REDISredis://redis:6379/0?decode_responses=TrueRedis connection string
MEILISEARCH_URLhttp://meilisearch:7700Meilisearch endpoint
MAX_WORKERS1Worker pool size (keep at 1)

Required Configuration

VariableRequiredDescription
ROOT_ADDRESSYesPublic URL (no trailing slash)
SECRET_KEYYes32+ character secret for JWT signing
STORAGE_BACKENDYeslocal or s3

Email Configuration

VariableRequiredDescription
MAIL_SERVERYesSMTP server hostname
MAIL_PORTYesSMTP server port (587 for TLS)
MAIL_USERNAMEYesSMTP authentication username
MAIL_PASSWORDYesSMTP authentication password
MAIL_ADDRESSYesSender email address
SKIP_EMAIL_VERIFICATIONNoSet to True to disable email verification

Storage Configuration

VariableRequiredDescription
STORAGE_PATHIf STORAGE_BACKEND=localAbsolute path for file storage
S3_ACCESS_KEYIf STORAGE_BACKEND=s3S3 access key
S3_SECRET_KEYIf STORAGE_BACKEND=s3S3 secret key
S3_BASE_URLIf STORAGE_BACKEND=s3S3 endpoint URL
S3_BUCKET_NAMENoS3 bucket name (default: classquiz)

OAuth Configuration

VariableRequiredDescription
GOOGLE_CLIENT_IDNoGoogle OAuth client ID
GOOGLE_CLIENT_SECRETNoGoogle OAuth client secret
GITHUB_CLIENT_IDNoGitHub OAuth client ID
GITHUB_CLIENT_SECRETNoGitHub OAuth client secret
CUSTOM_OPENID_PROVIDER__CLIENT_IDNoCustom OIDC client ID
CUSTOM_OPENID_PROVIDER__CLIENT_SECRETNoCustom OIDC client secret
CUSTOM_OPENID_PROVIDER__SERVER_METADATA_URLNoOIDC discovery URL

Optional Features

VariableRequiredDescription
HCAPTCHA_KEYNohCaptcha secret key
RECAPTCHA_KEYNoreCAPTCHA secret key
PIXABAY_API_KEYNoPixabay API key for image search
SENTRY_DSNNoSentry error tracking DSN
TELEMETRY_ENABLEDNoEnable usage telemetry (default: true)
FREE_STORAGE_LIMITNoStorage quota in bytes (default: 1GB)
MODSNoJSON array of moderator emails
REGISTRATION_DISABLEDNoDisable new registrations

Maintenance

Updating ClassQuiz

# Pull latest changes
git pull origin master

# Rebuild and restart
docker compose down
docker compose build
docker compose up -d

Database Backups

# Backup PostgreSQL
docker compose exec db pg_dump -U postgres classquiz > backup.sql

# Restore from backup
docker compose exec -T db psql -U postgres classquiz < backup.sql

View Logs

# All services
docker compose logs -f

# Specific service
docker compose logs -f api

# Last 100 lines
docker compose logs --tail=100 api

Restart Services

# Restart all
docker compose restart

# Restart specific service
docker compose restart api

Clean Up

# Remove containers and networks (preserves data)
docker compose down

# Remove everything including volumes (DATA LOSS!)
docker compose down -v

Troubleshooting

Symptoms: Redirect loops, OAuth errorsSolutions:
  1. Ensure ROOT_ADDRESS matches your public URL exactly (no trailing slash)
  2. Verify redirect URIs in OAuth provider match exactly
  3. Confirm HTTPS is enabled (OAuth requires secure connections)
  4. Check that VITE_GOOGLE_AUTH_ENABLED or VITE_GITHUB_AUTH_ENABLED is set in frontend/Dockerfile
  5. Rebuild frontend after environment variable changes:
    docker compose build frontend
    docker compose up -d frontend
    
Symptoms: “Connection refused” in browser console, games don’t startSolutions:
  1. Ensure reverse proxy forwards /socket.io/* to the API
  2. Verify WebSocket upgrade headers are set:
    Upgrade: websocket
    Connection: upgrade
    
  3. Check that HTTPS is enabled (WebSocket over HTTP fails in production)
  4. Test WebSocket endpoint:
    curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
      https://classquiz.yourdomain.com/socket.io/
    
Symptoms: Users don’t receive verification emailsSolutions:
  1. Verify SMTP credentials in docker-compose.yml
  2. Check API logs for email errors:
    docker compose logs api | grep -i mail
    
  3. Ensure firewall allows outbound SMTP (port 587 or 465)
  4. Test SMTP connection:
    docker compose exec api python -c "import smtplib; s=smtplib.SMTP('smtp.gmail.com', 587); s.starttls(); s.login('user', 'pass'); print('OK')"
    
  5. Temporarily disable verification for testing:
    SKIP_EMAIL_VERIFICATION: "True"
    
Symptoms: “Storage error” when uploading mediaSolutions:
  1. Local storage: Check directory permissions
    chmod -R 755 ./uploads
    docker compose restart api
    
  2. S3 storage: Verify credentials and bucket access
    docker compose exec api python -c "from classquiz.storage import Storage; s = Storage(...); print(s.test_connection())"
    
  3. Check storage limit:
    FREE_STORAGE_LIMIT: "5368709120"  # Increase to 5GB
    
  4. Verify allowed MIME types (source: classquiz/config.py:109):
    • Images: PNG, JPEG, GIF, WebP
    • Videos: MP4
Symptoms: “Table doesn’t exist” errorsSolutions:
  1. Run migrations manually:
    docker compose exec api alembic upgrade head
    
  2. Check migration status:
    docker compose exec api alembic current
    
  3. If migrations fail, check database connectivity:
    docker compose exec api python -c "from classquiz.db.models import db; print(db.database.url)"
    
Symptoms: Quiz search returns no resultsSolutions:
  1. Check Meilisearch status:
    docker compose ps meilisearch
    docker compose logs meilisearch
    
  2. Rebuild search index:
    docker compose exec api python -c "from classquiz.helpers.search import rebuild_index; rebuild_index()"
    
  3. Verify Meilisearch connection:
    curl http://localhost:7700/health
    

Security Best Practices

Secret Management

  • Generate unique SECRET_KEY for each deployment
  • Never commit secrets to version control
  • Rotate secrets periodically
  • Use environment files with restricted permissions

Network Security

  • Always use HTTPS in production
  • Restrict database access to Docker network
  • Keep services updated
  • Enable fail2ban for brute-force protection

Access Control

  • Set up moderators with MODS variable
  • Disable registration if running private instance
  • Enable captcha to prevent spam
  • Review OAuth scopes carefully

Backups

  • Backup database daily
  • Store media backups separately
  • Test restoration procedures
  • Keep backups encrypted

Performance Optimization

Scaling Considerations

Sufficient for most deployments (under 100 concurrent users):
  • Use local storage
  • Single instance of each service
  • PostgreSQL on same host

Resource Limits

Add to services in docker-compose.yml:
api:
  deploy:
    resources:
      limits:
        cpus: '2'
        memory: 2G
      reservations:
        cpus: '1'
        memory: 1G

Getting Help

Community Support

Join the Matrix Space for community support and discussions

Report Issues

Found a bug? Open an issue on GitHub

Documentation

Check the official docs for more information

Contribute

Want to contribute? See CONTRIBUTING.md
Remember: You must publish any modifications to the source code under the MPL 2.0 license. This protects the open-source nature of ClassQuiz.

Build docs developers (and LLMs) love