Complete guide to deploying WikiVisage on Wikimedia Toolforge using Build Service.
- A Toolforge account with a tool created (e.g.,
wikivisage) - An OAuth 2.0 consumer registered on Meta-Wiki with:
- OAuth version: 2.0 (not 1.0a)
- Applicable grants:
Basic rights+Edit existing pages - Callback URL:
https://wikivisage.toolforge.org/auth/callback
Note: OAuth consumer registration requires approval. This can take hours or days. Start this step first.
# Connect to the Toolforge bastion
ssh <username>@login.toolforge.org
# Switch to the tool account
become wikivisageAll subsequent commands assume you're running as the tool account.
Environment variables are how Build Service apps receive configuration (NFS home directory is not available inside containers).
cat ~/replica.my.cnf
# user = s<NNNNN>
# password = <password># Database
toolforge envvars create TOOL_TOOLSDB_USER "s<NNNNN>"
toolforge envvars create TOOL_TOOLSDB_PASSWORD "<your-tools-db-password>"
toolforge envvars create WIKIVISAGE_DB_NAME "s<NNNNN>__wikiface"
# OAuth 2.0 (from your consumer registration on Meta-Wiki)
toolforge envvars create OAUTH_CLIENT_ID "<client-id>"
toolforge envvars create OAUTH_CLIENT_SECRET "<client-secret>"
toolforge envvars create OAUTH_REDIRECT_URI "https://wikivisage.toolforge.org/auth/callback"
# Flask secret key (generate a strong random one)
toolforge envvars create FLASK_SECRET_KEY "$(python3 -c 'import secrets; print(secrets.token_hex(32))')"
# Token encryption (optional — encrypts OAuth tokens at rest in the DB)
# If unset, tokens are stored as plaintext (backward compatible)
toolforge envvars create WIKIVISAGE_TOKEN_KEY "$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')"
# Redis for shared rate limiting (optional — falls back to in-memory if unavailable)
# Default: redis://redis.svc.tools.eqiad1.wikimedia.cloud:6379
# Only needed if you want to customize the Redis URL
# toolforge envvars create WIKIVISAGE_REDIS_URL "redis://redis.svc.tools.eqiad1.wikimedia.cloud:6379"Verify with:
toolforge envvars list# Connect to ToolsDB
mariadb --defaults-file=$HOME/replica.my.cnf -h tools.db.svc.wikimedia.cloudCREATE DATABASE s<NNNNN>__wikiface;
exit;Important: The database name must follow the pattern
<tool-db-user>__<name>. ToolsDB only allows you to create databases prefixed with your credential username.
# Build from your GitHub repo
toolforge build start https://github.com/DiFronzo/WikiVisage.git
# Check build status (wait for "ok")
toolforge build showThe build uses Procfile, requirements.txt, and project.toml to create the container image. The image name is automatically tool-wikivisage/tool-wikivisage:latest.
Run as a one-off job:
toolforge jobs run migrate \
--command "python migrate.py" \
--image tool-wikivisage/tool-wikivisage:latest \
--mem 512MiThis creates all 9 tables (users, sessions, projects, images, faces, user_stats, sdc_claims, project_members, worker_heartbeat) and their indexes. Safe to re-run — migrations are idempotent.
Check the migration completed:
toolforge jobs logs migratetoolforge webservice buildservice start --mount noneThe Procfile runs gunicorn with 2 workers on port 8000.
Your app will be live at: https://wikivisage.toolforge.org
WikiVisage uses 2 concurrent worker instances for distributed processing. Workers claim projects via SELECT … FOR UPDATE with automatic stale-claim expiry (15 min).
Load both workers from jobs.yaml:
toolforge jobs load jobs.yamlThis starts two continuous jobs (ml-worker and ml-worker-2) that crawl Commons categories, download images, detect faces, and run the classification model. Each worker polls for new work every 60 seconds.
If jobs.yaml loading fails, start workers manually:
toolforge jobs run ml-worker \
--command 'python -u worker.py --worker-id ml-worker-1' \
--image tool-wikivisage/tool-wikivisage:latest \
--continuous --mem 3Gi
toolforge jobs run ml-worker-2 \
--command 'python -u worker.py --worker-id ml-worker-2' \
--image tool-wikivisage/tool-wikivisage:latest \
--continuous --mem 3GiCheck worker status:
toolforge jobs list
toolforge jobs logs ml-worker
toolforge jobs logs ml-worker-2- Health check: Visit
https://wikivisage.toolforge.org/health— should return{"status": "healthy", "database": "connected"} - Login: Click "Log in with Wikimedia" — should redirect to Meta for OAuth, then back to the dashboard
- Create a project: Enter a Wikidata Q-ID (e.g.,
Q42for Douglas Adams) and a Commons category - Worker activity: Check
toolforge jobs logs ml-worker— should show category traversal starting within 60 seconds
Releases trigger an automated deployment via .github/workflows/deploy.yml. The CD workflow:
- Builds a new container image from the release tag
- Runs schema migration
- Restarts both workers (
ml-workerandml-worker-2) - Restarts the web service
To deploy manually, use the workflow_dispatch trigger on the Actions tab with a git tag. The workflow also supports an optional database wipe (requires typing WIPE as confirmation).
# Web service logs
toolforge webservice logs
# Worker logs (both instances)
toolforge jobs logs ml-worker
toolforge jobs logs ml-worker-2The recommended approach is to create a GitHub release, which triggers the CD workflow automatically. For manual rebuilds:
# Rebuild the image
toolforge build start https://github.com/DiFronzo/WikiVisage.git
# Wait for build to finish
toolforge build show
# Restart web service
toolforge webservice restart
# Restart both workers (delete + run because jobs load doesn't restart unchanged jobs)
toolforge jobs delete ml-worker || true
toolforge jobs run ml-worker --command 'python -u worker.py --worker-id ml-worker-1' --image tool-wikivisage/tool-wikivisage:latest --continuous --mem 3Gi
toolforge jobs delete ml-worker-2 || true
toolforge jobs run ml-worker-2 --command 'python -u worker.py --worker-id ml-worker-2' --image tool-wikivisage/tool-wikivisage:latest --continuous --mem 3Gitoolforge webservice restarttoolforge webservice stop
toolforge jobs delete ml-worker
toolforge jobs delete ml-worker-2toolforge jobs run migrate \
--command "python migrate.py" \
--image tool-wikivisage/tool-wikivisage:latest \
--mem 512Mi# Delete and recreate (there's no "update" command)
toolforge envvars delete FLASK_SECRET_KEY
toolforge envvars create FLASK_SECRET_KEY "<new-value>"
# Restart services to pick up changes
toolforge webservice restart
toolforge jobs delete ml-worker || true
toolforge jobs run ml-worker --command 'python -u worker.py --worker-id ml-worker-1' --image tool-wikivisage/tool-wikivisage:latest --continuous --mem 3Gi
toolforge jobs delete ml-worker-2 || true
toolforge jobs run ml-worker-2 --command 'python -u worker.py --worker-id ml-worker-2' --image tool-wikivisage/tool-wikivisage:latest --continuous --mem 3GiThe web app checks if a worker has sent a heartbeat in the last 5 minutes. If you see this banner:
# Check if workers are running
toolforge jobs list
# Check worker logs for errors
toolforge jobs logs ml-worker
toolforge jobs logs ml-worker-2
# Restart workers
toolforge jobs delete ml-worker || true
toolforge jobs run ml-worker --command 'python -u worker.py --worker-id ml-worker-1' --image tool-wikivisage/tool-wikivisage:latest --continuous --mem 3Gi
toolforge jobs delete ml-worker-2 || true
toolforge jobs run ml-worker-2 --command 'python -u worker.py --worker-id ml-worker-2' --image tool-wikivisage/tool-wikivisage:latest --continuous --mem 3Gi
# Or reload from jobs.yaml
toolforge jobs load jobs.yaml- Verify the callback URL in your OAuth consumer matches exactly:
https://wikivisage.toolforge.org/auth/callback - Check that
OAUTH_CLIENT_ID,OAUTH_CLIENT_SECRET, andOAUTH_REDIRECT_URIare set:toolforge envvars list - Confirm the OAuth consumer has been approved on Meta-Wiki
# Check build logs
toolforge build show
# Common causes:
# - requirements.txt has a broken dependency
# - project.toml references a package not in Ubuntu 24.04 repos- Verify credentials:
toolforge envvars list - Verify the database exists:
mariadb --defaults-file=$HOME/replica.my.cnf -h tools.db.svc.wikimedia.cloud -e "SHOW DATABASES LIKE '%wikiface%'" - Check that migration has run: look for 9 tables in the database