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.ScannerError—config.ymlis 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 inRT_DATABASE_URLis not writable, or does not exist. See data vanished below for the four-slash trap. -
ModuleNotFoundError—requirements.txtchanged 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.
- 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')"
- Re-seed:
bash
docker compose run --rm web flask seed
- 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 overfile://. 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_10is 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 theRT_DATABASE_URLoverride 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.