Production correct-credential login returned auth_unavailable 503 because
Flask's SECRET_KEY was unset: wrong-password probes stopped at 401 before
CSRF token issuance, while valid credentials reached _csrf_serializer()
and crashed. App factory now rejects absent/short (<32 char) SECRET_KEY at
startup, and docker_entrypoint.sh fails fast before migration/services.
Bootstrap no longer passes ADMIN_PASSWORD in process arguments; env-only.
Tests: app-factory + entrypoint regression (5 focused passed), full
backend suite 204 passed. Independent review PASS.
With this, a fresh deploy provisions its own first super_admin automatically:
after alembic migrations the entrypoint runs scripts/bootstrap_super_admin.py
when ADMIN_EMAIL + ADMIN_PASSWORD are set (idempotent, never overwrites an
existing password; org name/slug optional). This removes the chicken-egg where
login needs a user but no UI/seed could create the very first one.
Optionally runs only when both env vars are present, so an existing deployment
is unaffected. Verified: bash syntax ok; fresh DB produced org+password+
super_admin; re-run left the existing password unchanged.
Worker crash-loop root cause (from container log):
sqlalchemy.exc.NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:postgres
Two compounding issues:
1. Dockerfile copied pyproject.toml/uv.lock, ran 'uv sync --frozen',
then 'COPY backend ./backend' which OVERWROTE those dep files with the
shipped versions. The two could differ, so every entrypoint 'uv run'
detected drift and REBUILT/re-synced the project at container runtime
(seen as repeated 'Building crowdsight-backend...' + 'Uninstalled N /
Installed 1'), never installing the psycopg Postgres driver that the
image's own lock actually lists.
2. Result: alembic upgrade head over a postgres DATABASE_URL crashed with
NoSuchModuleError -> worker crash-loop.
Fix:
- Dockerfile: COPY backend (full source) BEFORE 'uv sync --frozen --no-dev',
so the installed deps match the shipped pyproject.toml/uv.lock exactly.
- Use 'uv run --frozen' for alembic/gunicorn/worker so nothing re-syncs at
runtime.
- entrypoint: fail fast with a clear message if DATABASE_URL is postgres
but psycopg is missing (instead of a confusing alembic traceback).
Verified: entrypoint bash syntax ok; 'uv run --frozen ... import psycopg'
passes; psycopg present in git-tracked uv.lock + pyproject.
Production image had no DB migration step, so a fresh container had an
empty database: the durable worker queried the 'jobs' table before it
existed and crash-looped with sqlalchemy OperationalError 'no such table:
jobs' (supervisor restart loop).
- Add backend/docker_entrypoint.sh: fail-fast if DATABASE_URL is unset,
run 'alembic upgrade head' (idempotent), then exec supervisord.
- Dockerfile CMD now runs the entrypoint.
- supervisor: fix nodaemon typo, stream stdout/stderr to /dev/stdout +
/dev/stderr so worker errors are visible in container logs, and give
worker startsecs/startretries.
Verified locally: entrypoint bash syntax ok, alembic upgrade head
idempotent, jobs + 19 tables created, worker --once exits 0 after migrate
(previously exit 1 with no-such-table). Worker/schema tests 11 passed.