# Kadence — one image that serves the API and the web UI. # # Stage 1 builds the React/Material front end; stage 2 installs the Python # backend and copies those built files into `static/`, which FastAPI serves from # the same origin. One container, one port, no CORS to configure. # ---- stage 1: the web UI --------------------------------------------------- FROM node:22-alpine AS web WORKDIR /web COPY frontend/package.json frontend/package-lock.json* ./ RUN npm ci --no-audit --no-fund || npm install --no-audit --no-fund COPY frontend/ ./ RUN npm run build # ---- stage 2: the backend -------------------------------------------------- FROM python:3.12-slim AS runtime ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=1 \ TZ=Europe/Moscow WORKDIR /app # Build deps for psycopg are already covered by the binary wheel; curl is here # for the healthcheck only. RUN apt-get update \ && apt-get install -y --no-install-recommends curl tzdata \ && rm -rf /var/lib/apt/lists/* COPY backend/pyproject.toml ./ COPY backend/app ./app RUN pip install --no-cache-dir \ "fastapi>=0.115" "uvicorn[standard]>=0.30" "sqlalchemy>=2.0" \ "psycopg[binary]>=3.2" "pydantic>=2.8" "pydantic-settings>=2.4" \ "httpx>=0.27" "beautifulsoup4>=4.12" "apscheduler>=3.10,<4" \ "python-dotenv>=1.0" "tzdata>=2024.1" COPY --from=web /web/dist ./static # The app writes nothing outside the database, so it can run unprivileged. RUN useradd --create-home --uid 10001 kadence && chown -R kadence:kadence /app USER kadence EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD curl -fsS http://localhost:8000/api/health || exit 1 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]