FROM node:22-alpine AS pyodide-assets ARG PYODIDE_VERSION=0.27.7 WORKDIR /build COPY scripts/vendor_pyodide_packages.mjs ./vendor_pyodide_packages.mjs RUN npm install --global --ignore-scripts --omit=dev pyodide@${PYODIDE_VERSION} codemirror@5.65.16 jszip@3.10.1 \ && node ./vendor_pyodide_packages.mjs /usr/local/lib/node_modules/pyodide https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/ FROM python:3.11-slim AS python-dependencies ARG PIP_TOOLS_VERSION=7.6.1 ARG PIP_AUDIT_VERSION=2.10.1 WORKDIR /build COPY requirements.txt . RUN python -m pip install --no-cache-dir pip-tools==${PIP_TOOLS_VERSION} pip-audit==${PIP_AUDIT_VERSION} \ && pip-compile --resolver=backtracking --generate-hashes --output-file=requirements.lock requirements.txt \ && pip-audit --strict -r requirements.lock FROM python:3.11-slim # System deps for OpenCV headless, Pygame, MediaPipe + CJK fonts RUN apt-get update && apt-get install -y --no-install-recommends \ libglib2.0-0 \ libgl1 \ libgomp1 \ libgles2 \ libegl1 \ bubblewrap \ util-linux \ fonts-wqy-zenhei \ fonts-wqy-microhei \ fonts-noto-cjk \ && rm -rf /var/lib/apt/lists/* ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ SDL_VIDEODRIVER=dummy \ SDL_AUDIODRIVER=dummy \ PORT=7860 \ MAX_CONCURRENT_RUNNERS=3 \ RUNNER_ISOLATION_MODE=process \ RUNNER_STARTS_PER_IP_PER_HOUR=120 \ RUNNER_START_COOLDOWN_SEC=0.75 \ SESSION_TIMEOUT_SEC=900 \ PROJECT_CACHE_TTL_SEC=10 \ ENABLE_HSTS=1 \ CLASSROOM_COURSE_QUOTA_MB=1024 \ CLASSROOM_MAX_VERSIONS_PER_KIND=100 \ CLASSROOM_CHECKPOINTS_PER_HOUR=60 \ CLASSROOM_SUBMISSIONS_PER_HOUR=20 \ CLASSROOM_VERSION_COOLDOWN_MS=1500 \ CLASSROOM_WS_CONNECTIONS_PER_MINUTE=300 \ CLASSROOM_WS_MESSAGES_PER_MINUTE=300 \ RUNNER_MEMORY_LIMIT_MB=3072 \ RUNNER_CPU_LIMIT_SEC=900 \ RUNNER_TMP_LIMIT_MB=256 \ RUNNER_PROJECT_LIMIT_MB=128 \ RUNNER_PROJECT_FILE_LIMIT=2000 WORKDIR /app COPY --from=python-dependencies /build/requirements.lock /tmp/requirements.lock COPY requirements.txt . # Base image without TensorFlow (smaller / faster cold start). # To include Keras demos: COPY requirements-ml.txt . && pip install -r requirements-ml.txt RUN python -m pip install --no-cache-dir --require-hashes -r /tmp/requirements.lock COPY . . COPY --from=python-dependencies /build/requirements.lock /app/requirements.lock COPY --from=pyodide-assets /usr/local/lib/node_modules/pyodide /app/classroom_static/vendor/pyodide COPY --from=pyodide-assets /usr/local/lib/node_modules/codemirror/lib/codemirror.js /app/classroom_static/vendor/codemirror/codemirror.js COPY --from=pyodide-assets /usr/local/lib/node_modules/codemirror/lib/codemirror.css /app/classroom_static/vendor/codemirror/codemirror.css COPY --from=pyodide-assets /usr/local/lib/node_modules/codemirror/theme/material-darker.css /app/classroom_static/vendor/codemirror/material-darker.css COPY --from=pyodide-assets /usr/local/lib/node_modules/codemirror/mode/python/python.js /app/classroom_static/vendor/codemirror/python.js COPY --from=pyodide-assets /usr/local/lib/node_modules/jszip/dist/jszip.min.js /app/classroom_static/vendor/jszip/jszip.min.js RUN groupadd --gid 10001 telearn \ && useradd --uid 10001 --gid 10001 --create-home --shell /usr/sbin/nologin telearn \ && mkdir -p /app/projects /app/models /app/static /app/classroom_data /tmp/telearn_runner /tmp/telearn_classroom \ && chown -R telearn:telearn /app/projects /app/classroom_data /tmp/telearn_runner /tmp/telearn_classroom \ && chmod 700 /tmp/telearn_runner /tmp/telearn_classroom # Healthcheck for orchestrators HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:%s/health' % __import__('os').environ.get('PORT','7860'))" || exit 1 USER telearn CMD uvicorn server:app --host 0.0.0.0 --port $PORT --ws-max-size 1048576 --ws-max-queue 8