# Provider-neutral production image for the backend (API and Celery worker # share one image; the worker is started with a command override). # # Build contract (owned with the backend team, see infra/README.md): # - build context is backend/ # - backend/ contains pyproject.toml, uv.lock and the application package app/ # - API entrypoint: uvicorn app.main:app; health endpoint: GET /healthz # - Celery app import path: app.worker.celery # # Build: # docker build -f infra/docker/Dockerfile.backend -t finance-api:local backend FROM python:3.14-slim-bookworm AS runtime ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ UV_LINK_MODE=copy \ UV_PROJECT_ENVIRONMENT=/opt/venv # Pin the uv digest before first production build; keep in sync with CI. COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv WORKDIR /app # Dependencies first for layer caching. Requires uv.lock to be up to date. COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev --no-install-project # Application code. COPY app ./app COPY alembic ./alembic COPY alembic.ini ./alembic.ini RUN uv sync --frozen --no-dev RUN useradd --uid 10001 --create-home --shell /usr/sbin/nologin appuser \ && chown -R appuser:appuser /app USER appuser ENV PATH="/opt/venv/bin:${PATH}" EXPOSE 8000 # API role default. Database migrations run as a one-shot release step # (see docs/operations/beta-release-runbook.md), not on container start. CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips", "*"]