agnes-the-ai-analyst/Dockerfile
ZdenekSrotyr 103669dafd
fix(cli-install): move kbcstorage to [server] extra so wheel installs cleanly (P0 onboarding hotfix → 0.53.4) (#272)
* fix(cli-install): move kbcstorage to [server] extra so wheel installs cleanly

The 0.53.3 wheel served at /cli/wheel/ is unsatisfiable on a clean machine:
analyst runs `uv tool install <wheel-url>` per the published /setup
instructions and the resolver immediately fails with

    Because kbcstorage<=0.9.5 depends on urllib3<2.0.0 and
    agnes-the-ai-analyst==0.53.3 depends on kbcstorage>=0.9.0 and
    urllib3>=2.7.0, we can conclude that agnes-the-ai-analyst==0.53.3
    cannot be used.

The `[tool.uv] override-dependencies = ["urllib3>=2.7.0"]` in pyproject.toml
masked the conflict in workspace contexts (Dockerfile + dev install) but
does NOT propagate to the wheel — wheel METADATA is plain PEP 621
Requires-Dist, and a fresh resolver context (uv tool install <wheel-url>)
never sees the override. Every existing test passed because the dev venv
already has kbcstorage 0.9.5 + urllib3 2.7.0 coexisting under workspace
overrides; the break only surfaces on the next analyst's first install.

Fix: kbcstorage moved out of [project] dependencies into
[project.optional-dependencies].server, since it is server-side only
(connectors/keboola/client.py is the sole import site, called from admin
endpoints, server connectors, and integration tests — never from the CLI
install path). Server install picks it up via Dockerfile's
`uv pip install --system --no-cache ".[server]"`. CI installs `.[dev,server]`
so workspace tests still cover the kbcstorage path. Analyst CLI wheel
METADATA now lists `kbcstorage>=0.9.0; extra == 'server'` (gated) and
`uv tool install <wheel>` resolves cleanly.

Verified end-to-end:
- Built wheel locally; inspected METADATA — kbcstorage line is now `; extra == 'server'`.
- `docker run --rm python:3.13-slim` + `uv tool install <wheel>`: agnes 0.53.4 installs, `agnes --version` works, `agnes catalog --help` renders, kbcstorage absent from CLI venv, urllib3 = 2.7.0.
- Same container with `.[server]` install path: kbcstorage present, urllib3 = 2.7.0 (override applies in workspace context).
- Full pytest suite green locally (4157 passed, 25 skipped).

* release: 0.53.4 — analyst CLI install hotfix (urllib3/kbcstorage resolver conflict)

Patch bump shipping the [server] extra split + new clean-install CI lane.
No DB migration; no API change; no operator-facing config change.
Operator side (Dockerfile path) auto-picks `.[server]` so the production
image gains kbcstorage transparently. Analyst onboarding (uv tool install
<wheel>) starts working again.
2026-05-12 17:09:44 +00:00

74 lines
3.2 KiB
Docker

FROM python:3.13-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl git && rm -rf /var/lib/apt/lists/*
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ARG AGNES_VERSION=dev
ARG RELEASE_CHANNEL=dev
ARG AGNES_COMMIT_SHA=unknown
ARG AGNES_TAG=unknown
ENV AGNES_VERSION=${AGNES_VERSION}
ENV RELEASE_CHANNEL=${RELEASE_CHANNEL}
ENV AGNES_COMMIT_SHA=${AGNES_COMMIT_SHA}
ENV AGNES_TAG=${AGNES_TAG}
WORKDIR /app
COPY . .
# Bake every host-side artifact at /opt/agnes-host/ — the contract path
# VM startup uses to extract files via `docker create` + `docker cp`
# instead of curling from raw.githubusercontent.com/main. Pins host
# artifacts to AGNES_TAG the same way the app is already pinned —
# eliminates the split-brain where the immutable image runs against
# arbitrary main-branch compose files / bash scripts.
#
# Includes:
# - agnes-auto-upgrade.sh — host cron driver (5-min digest poll)
# - agnes-tls-rotate.sh — host cron driver (daily corp-PKI cert refetch)
# - tls-fetch.sh — generic URL fetcher (sm:// gs:// https:// file://)
# - docker-compose.{yml,prod.yml,host-mount.yml,tls.yml} — host runtime
# - Caddyfile — TLS reverse proxy config
#
# Why a copy out of /app instead of pointing at /app directly:
# /app is owned by uid 999 (USER agnes below); /opt/agnes-host is
# root-owned, mode 0755 across the board, stable path that won't
# shift if /app structure refactors. Stable contract for `docker cp`
# consumers.
RUN mkdir -p /opt/agnes-host && \
cp /app/scripts/ops/agnes-auto-upgrade.sh \
/app/scripts/ops/agnes-tls-rotate.sh \
/app/scripts/tls-fetch.sh \
/opt/agnes-host/ && \
cp /app/docker-compose.yml /app/docker-compose.prod.yml \
/app/docker-compose.host-mount.yml /app/docker-compose.tls.yml \
/app/Caddyfile /opt/agnes-host/ && \
chmod 0755 /opt/agnes-host/agnes-auto-upgrade.sh \
/opt/agnes-host/agnes-tls-rotate.sh \
/opt/agnes-host/tls-fetch.sh && \
chmod 0644 /opt/agnes-host/docker-compose.yml \
/opt/agnes-host/docker-compose.prod.yml \
/opt/agnes-host/docker-compose.host-mount.yml \
/opt/agnes-host/docker-compose.tls.yml \
/opt/agnes-host/Caddyfile
# Build wheel artifact (served at /cli/download)
RUN uv build --wheel --out-dir /app/dist
# Install production dependencies from pyproject.toml. The `[server]` extra
# pulls in connectors-only deps (kbcstorage) that the CLI wheel deliberately
# omits — see [project.optional-dependencies].server in pyproject.toml.
RUN uv pip install --system --no-cache ".[server]"
# Run as non-root user for container hardening (C13).
# uid/gid pinned to 999 so host-side chown in startup-script.sh.tpl can match
# without parsing /etc/passwd inside the image. Changing this number breaks
# every existing PD-backed deploy until the operator re-chowns /data.
RUN useradd --system --uid 999 --create-home --shell /usr/sbin/nologin agnes && \
mkdir -p /data && chown -R agnes:agnes /data && \
chown -R agnes:agnes /app
USER agnes
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips", "*"]