Troubleshooting#

Start here, always:

docker compose ps          # is it running, is it healthy?
docker compose logs --tail=100 web
curl -s http://localhost:8080/healthz

Nearly every problem below announces itself clearly in those three outputs.

The app will not start#

Bind for 0.0.0.0:8080 failed: port is already allocated#

Something else owns the port.

lsof -nP -iTCP:8080 -sTCP:LISTEN     # macOS / Linux

Either stop that process or change RT_HTTP_PORT in .env and docker compose up -d again. Only the host side of the mapping changes; the container always listens on 8000.

The container restarts in a loop#

docker compose logs --tail=200 web

Look for the traceback right before the restart. The usual causes:

  • yaml.scanner.ScannerErrorconfig.yml is not valid YAML. Tabs are the usual culprit; YAML requires spaces. Validate it:

bash docker compose run --rm web python -c \ "import yaml; yaml.safe_load(open('/app/app/config/config.yml'))"

  • sqlalchemy.exc.OperationalError: unable to open database file — the path in RT_DATABASE_URL is not writable, or does not exist. See data vanished below for the four-slash trap.

  • ModuleNotFoundErrorrequirements.txt changed but the image was not rebuilt: docker compose build web && docker compose up -d.

docker compose says the file is invalid#

You are probably running the old standalone docker-compose (v1). This project needs Compose v2, invoked as a Docker subcommand:

docker compose version      # should print v2.x

The library is empty#

Symptom: Algorithms shows no cases, or the counts are not 41 / 57 / 21.

  1. Check the data file exists and is non-trivial:

bash docker compose run --rm web python -c \ "import json; d=json.load(open('/app/app/data/algorithms.json')); \ print(len(d['cases']), 'cases,', len(d['groups']), 'groups')"

  1. Re-seed:

bash docker compose run --rm web flask seed

  1. If the data file itself is missing or stale, regenerate it from the PDFs in pdf/:

bash docker compose run --rm web python scripts/extract_algorithms.py docker compose run --rm web flask seed --force

--force rebuilds the library from scratch. It can orphan attempts and assessments if case keys changed, so back up first.

My data vanished#

Almost always one of two things.

The wrong number of slashes. In a SQLite URL, three slashes is a relative path and four is absolute:

RT_DATABASE_URL=sqlite:///data/rubiks-trainer.db     # relative to /app — NOT on the volume
RT_DATABASE_URL=sqlite:////data/rubiks-trainer.db    # /data — on the rt-data volume

With three slashes the database lands inside the container's writable layer and disappears the next time the container is recreated. Check where the app actually thinks the database is:

docker compose exec web python -c \
  "from app.config import flask_config; print(flask_config()['SQLALCHEMY_DATABASE_URI'])"
docker compose exec web ls -la /data

docker compose down -v. The -v flag removes named volumes, including rt-data. There is no undo. Restore from a backup.

To check the volume is intact:

docker volume ls | grep rt-data
docker compose exec web ls -la /data

Everything is a different profile than I expected#

The active profile lives in the session cookie, so it is per browser and per browser profile. A private window, a different browser or a cleared cookie jar all give you the default profile again. Click the profile chip in the header to switch back. Nothing was lost — the data is keyed to the profile row, not to the cookie.

If the cookie stops sticking entirely, RT_SECRET_KEY is probably changing between restarts (for example if it is being generated in the compose file rather than written into .env). Every change invalidates all sessions.

The 3D viewer is blank#

  • No WebGL. The viewer detects this and falls back to the flat net with a message. If you get an empty box rather than the net, that is a bug — please report it with the browser console output.
  • Console errors about modules. The viewer is loaded with <script type="module">, which browsers refuse to load over file://. It must be served by the app.
  • A stale vendored library. Hard-reload (Cmd/Ctrl + Shift + R). The vendored three.js is served as a static file and browsers cache it hard.

Check the browser console. The app ships with no console errors; anything you see there is a real signal.

Times look wrong or scores will not move#

  • Scores need three attempts. Below targets.min_attempts_for_measured (default 3), a case's score still comes from your self-assessment and is capped at 80. Do a third rep.
  • DNFs do not count toward times. They count as attempts but are excluded from the averages, by design.
  • The goal is per profile. If every case suddenly looks red, check the profile's goal — a profile set to sub_10 is measured against very tight targets.

/docs shows nothing, or a page 404s#

The docs tree is bind-mounted at /app/docs. Confirm the container can see it:

docker compose exec web ls /app/docs
docker compose exec web ls /app/docs/user

If the directory is empty inside the container, the mount is missing from docker-compose.yml. If a specific page 404s, the file name and the URL must match exactly, minus the .md: docs/user/library.md is served at /docs/user/library. Names are case-sensitive.

The tree is re-read whenever a file's modification time changes, so edits show up on reload without a restart. If they do not, you are editing a file outside the mounted directory, or docs.root in config.yml points somewhere else.

Tests fail#

Always run them in the container, with a throw-away database:

docker compose run --rm -e RT_DATABASE_URL=sqlite:////tmp/t.db web pytest
  • sqlite3.OperationalError: attempt to write a readonly database — you omitted the RT_DATABASE_URL override and the suite tried to use the real one.
  • Import errors for app.* — you are running pytest on the host. Do not.
  • JS suite fails to start — it runs in a separate service: docker compose run --rm jstest.

See Testing for the full picture.

Permission errors writing to /data or /app/instance#

The image runs as the unprivileged appuser. If you have bind-mounted a host directory over /data, its ownership on the host has to allow that user to write:

docker compose exec web id
docker compose exec web touch /data/.write-test && echo ok

The stock setup uses a named volume, which Docker creates with the right ownership, so this only comes up if you changed the mount.

Still stuck#

Collect this and open an issue:

docker compose version
docker compose ps
docker compose logs --tail=200 web
docker compose exec web python -c \
  "from app.config import load_settings; import json; print(json.dumps(load_settings(), indent=2))"

Redact RT_SECRET_KEY and any database credentials before you post it.

Source: docs/install/troubleshooting.md