# syntax=docker/dockerfile:1.7 # Parking VISION image: the Python/uv ANPR microservice. Build CONTEXT is apps/vision # (self-contained Python package; no monorepo deps). Ships WITH the `alpr` extra (real # fast-alpr/onnxruntime stack) but the engine is env-selected: VISION_RECOGNIZER=stub # (default, boots anywhere) or fast_alpr (prod). See wiki/decisions/container-deployment.md, # wiki/decisions/vision-service-packaging.md. # uv-provided Python 3.12 (matches apps/vision/.python-version). FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS base WORKDIR /app ENV UV_LINK_MODE=copy \ UV_COMPILE_BYTECODE=1 \ PYTHONUNBUFFERED=1 # System libs the recognizer stack needs (opencv/onnxruntime): GL + glib. Kept minimal. RUN apt-get update \ && apt-get install -y --no-install-recommends libgl1 libglib2.0-0 \ && rm -rf /var/lib/apt/lists/* # ---- deps: resolve + install the venv from the lockfile (cache-friendly) ---- # Manifests first so the heavy `uv sync` layer caches across source edits. COPY pyproject.toml uv.lock .python-version ./ RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --no-install-project --extra alpr # ---- project source ---- COPY vision_service/ ./vision_service/ COPY README.md ./ RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --extra alpr # Non-root runtime user, created BEFORE the model pre-warm so the weights cache lands in # this user's HOME (~/.cache) — the SAME path the runtime reads. (fast-alpr's # open-image-models caches under $HOME/.cache/open-image-models keyed to HOME, ignoring # HF_HOME/XDG_CACHE_HOME — so the pre-warm MUST run as the runtime user, not root.) RUN useradd --system --create-home --uid 999 vision \ && chown -R vision:vision /app USER vision # Pre-warm the fast-alpr model weights INTO the image (as the vision user → /home/vision/ # .cache) so the prod recognizer is OFFLINE-first: ALPR() downloads weights on first # construction, which would otherwise need network on the appliance's first scan. Best-effort # — if the build host has no network this is skipped and weights fetch lazily at runtime. # NB: NO --mount=type=cache here — a BuildKit cache mount at ~/.cache is NOT committed to the # image layer, so the downloaded weights would vanish. They must write to the real layer. RUN uv run python -c "from fast_alpr import ALPR; ALPR()" \ || echo "[build] model pre-warm skipped (no network) — weights fetch at runtime" # Default to the stub recognizer (offline, no model load); override to fast_alpr in prod. ENV VISION_RECOGNIZER=stub \ VISION_HOST=0.0.0.0 \ VISION_PORT=8089 EXPOSE 8089 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8089/health').status==200 else 1)" || exit 1 CMD ["uv", "run", "uvicorn", "vision_service.app:app", "--host", "0.0.0.0", "--port", "8089"]