fix: run alembic migrations before starting services
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.
This commit is contained in:
@@ -43,6 +43,9 @@ COPY backend ./backend
|
|||||||
COPY locales ./locales
|
COPY locales ./locales
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
|
|
||||||
|
# Make the migration-runner entrypoint executable
|
||||||
|
RUN chmod +x /app/backend/docker_entrypoint.sh
|
||||||
|
|
||||||
# Copy built SPA into nginx web root
|
# Copy built SPA into nginx web root
|
||||||
COPY --from=frontend-build /build/frontend/dist /usr/share/nginx/html
|
COPY --from=frontend-build /build/frontend/dist /usr/share/nginx/html
|
||||||
|
|
||||||
@@ -51,12 +54,12 @@ RUN echo 'server {\n listen 8080;\n server_name _;\n root /usr/share/nginx/ht
|
|||||||
&& ln -sf /etc/nginx/sites-available/crowdsight /etc/nginx/sites-enabled/crowdsight \
|
&& ln -sf /etc/nginx/sites-available/crowdsight /etc/nginx/sites-enabled/crowdsight \
|
||||||
&& rm -f /etc/nginx/sites-enabled/default
|
&& rm -f /etc/nginx/sites-enabled/default
|
||||||
|
|
||||||
# supervisor: run nginx + gunicorn + worker
|
# supervisor: run nginx + gunicorn + worker (migrations already run by entrypoint)
|
||||||
RUN echo '[supervisord]\nnodeamon=false\n\n[program:nginx]\ncommand=/usr/sbin/nginx -g "daemon off;"\nautostart=true\nautorestart=true\n\n[program:backend]\ncommand=/bin/bash -c "cd /app/backend && uv run gunicorn -w 2 -b 0.0.0.0:5001 --timeout 120 wsgi:app"\nautostart=true\nautorestart=true\n\n[program:worker]\ncommand=/bin/bash -c "cd /app/backend && uv run python worker.py --poll-interval 5"\nautostart=true\nautorestart=true\n' > /etc/supervisor/conf.d/crowdsight.conf
|
RUN echo '[supervisord]\nnodaemon=true\nlogfile=/var/log/supervisor/supervisord.log\npidfile=/var/run/supervisord.pid\n\n[program:nginx]\ncommand=/usr/sbin/nginx -g "daemon off;"\nautostart=true\nautorestart=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n\n[program:backend]\ncommand=/bin/bash -c "cd /app/backend && uv run gunicorn -w 2 -b 0.0.0.0:5001 --timeout 120 wsgi:app"\nautostart=true\nautorestart=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n\n[program:worker]\ncommand=/bin/bash -c "cd /app/backend && uv run python worker.py --poll-interval 5"\nautostart=true\nautorestart=true\nstartsecs=2\nstartretries=5\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n' > /etc/supervisor/conf.d/crowdsight.conf
|
||||||
|
|
||||||
EXPOSE 8080 5001
|
EXPOSE 8080 5001
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
|
||||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:5001/health', timeout=4)" || exit 1
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:5001/health', timeout=4)" || exit 1
|
||||||
|
|
||||||
CMD ["/usr/bin/supervisord", "-n"]
|
CMD ["/app/backend/docker_entrypoint.sh"]
|
||||||
|
|||||||
26
backend/docker_entrypoint.sh
Normal file
26
backend/docker_entrypoint.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Docker entrypoint for CrowdSight production image.
|
||||||
|
#
|
||||||
|
# Runs database migrations to head BEFORE starting any service, so that the
|
||||||
|
# backend (gunicorn) and the durable worker never query tables that do not
|
||||||
|
# exist yet (fixes worker crash-loop: "no such table: jobs").
|
||||||
|
#
|
||||||
|
# - Require DATABASE_URL to be set (fail fast with a clear message).
|
||||||
|
# - Run `alembic upgrade head` (idempotent; no-ops when already at head).
|
||||||
|
# - Then exec supervisord to run nginx + backend + worker.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd /app/backend
|
||||||
|
|
||||||
|
if [[ -z "${DATABASE_URL:-}" ]]; then
|
||||||
|
echo "[entrypoint] FATAL: DATABASE_URL is not set. Refusing to start." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[entrypoint] Running database migrations (alembic upgrade head)..."
|
||||||
|
uv run alembic upgrade head
|
||||||
|
echo "[entrypoint] Migrations complete."
|
||||||
|
|
||||||
|
echo "[entrypoint] Starting supervisord (nginx + backend + worker)..."
|
||||||
|
exec /usr/bin/supervisord -n
|
||||||
Reference in New Issue
Block a user