From 9e1d6026b5545d33ad539176972a3dcbddc70e56 Mon Sep 17 00:00:00 2001 From: irl Date: Mon, 22 Jun 2026 14:24:34 +0100 Subject: [PATCH] feat: adds Containerfile with frontend serving --- .python-version | 2 +- Containerfile | 42 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 6 ++++-- src/main.py | 2 ++ uv.lock | 11 +++++++---- 5 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 Containerfile diff --git a/.python-version b/.python-version index 6324d40..e4fba21 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.14 +3.12 diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..ab6c9c4 --- /dev/null +++ b/Containerfile @@ -0,0 +1,42 @@ +FROM node:22-slim AS react-builder + +WORKDIR /app +COPY frontend/ /app/ +RUN --mount=type=cache,target=/root/.npm npm ci +RUN npm run build # Outputs to /app/dist + +FROM ghcr.io/astral-sh/uv:python3.12-trixie-slim AS python-builder + +ENV UV_PYTHON_DOWNLOADS=0 + +WORKDIR /app + +# Install dependencies first (layer caching) +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --locked --no-install-project --no-editable + +# Copy project source and install the project itself +COPY ./ /app/ +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --locked --no-editable + + +FROM python:3.12-slim-trixie + +WORKDIR /app + +COPY alembic /app/alembic +COPY alembic.ini /app +COPY src /app/src +COPY --from=python-builder /app/.venv /app/.venv +COPY --from=react-builder /app/dist /app/static + +# Ensure venv is on PATH +ENV PATH="/app/.venv/bin:$PATH" \ + UV_PYTHON_DOWNLOADS=0 + +EXPOSE 8000 + +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/pyproject.toml b/pyproject.toml index bd23ff7..bb7d902 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ requires-python = ">=3.12" dependencies = [ "alembic>=1.18.4", "email-validator>=2.3.0", - "fastapi>=0.136.3", + "fastapi>=0.138.0", "httptools>=0.7.1", "httpx>=0.28.1", "itsdangerous>=2.2.0", @@ -34,11 +34,13 @@ line-length = 92 [tool.ruff.format] quote-style = "double" -indent-style = "tab" [tool.uv] add-bounds = "major" exclude-newer = "P2W" +exclude-newer-package = { + "fastapi" = "2026-06-22T00:00:00Z" +} [dependency-groups] dev = [ diff --git a/src/main.py b/src/main.py index bf671db..022cb6f 100644 --- a/src/main.py +++ b/src/main.py @@ -77,3 +77,5 @@ if settings.DISABLE_AUTH and (settings.ENVIRONMENT == Environment.LOCAL): app.include_router(api_router) + +app.frontend("/ui", directory="/app/static", fallback="index.html") diff --git a/uv.lock b/uv.lock index f4be9e1..147ce12 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,9 @@ requires-python = ">=3.12" exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P2W" +[options.exclude-newer-package] +fastapi = "2026-06-22T00:00:00Z" + [[package]] name = "alembic" version = "1.18.4" @@ -238,7 +241,7 @@ dev = [ requires-dist = [ { name = "alembic", specifier = ">=1.18.4" }, { name = "email-validator", specifier = ">=2.3.0" }, - { name = "fastapi", specifier = ">=0.136.3" }, + { name = "fastapi", specifier = ">=0.138.0" }, { name = "httptools", specifier = ">=0.7.1" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "itsdangerous", specifier = ">=2.2.0" }, @@ -349,7 +352,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.136.3" +version = "0.138.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -358,9 +361,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/58/ff455d9fe47c60abadb34b9e05a304b1f05f5ab8000ac01565156b6f5e43/fastapi-0.138.0.tar.gz", hash = "sha256:d445a4877636ad191e7053e08c9bf98cb921a6756776848400bb773d1740c061", size = 419240, upload-time = "2026-06-20T01:18:05.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ff/8496d9847a5fedae775eb49460722d3efaa80487854273e9647ae876218c/fastapi-0.138.0-py3-none-any.whl", hash = "sha256:b6f54fd1bd72c80b0f899f172c61a600f6f7af9b43d4d772a018f35624048cb0", size = 126779, upload-time = "2026-06-20T01:18:03.483Z" }, ] [[package]]