scaffold: project skeleton, schema, healthz/readyz, CI
Initial project structure for neuronetz-gateway per scope-docs/SPEC.md: - Python 3.12 / FastAPI / SQLAlchemy 2.0 (async) / Redis / Postgres stack managed by uv. Multi-stage non-root Dockerfile, prod + dev compose files (ollama service is NEVER published in either), Caddyfile + systemd unit, justfile, GitHub Actions CI (ruff, mypy --strict, pytest, bandit, pip-audit). - Pydantic-Settings config covering every env var from SPEC §7, including the MODEL_DISCOVERY_* keys for the dynamic-discovery feature (§4.6). - Alembic 0001_initial creates the full gateway schema (8 tables, 3 enums, notify_key_revoked() trigger), incl. allow_all_models on tenant_limits and key_limits for the per-tenant auto-grant toggle. - Working /healthz, /readyz (fail-closed when deps unreachable), and a Prometheus /metrics stub. Sanitizing error handlers that attach X-Request-ID to every response and never leak upstream internals. - SPEC + AGENT_PROMPT included under scope-docs/ (source of truth).
This commit is contained in:
97
Dockerfile
Normal file
97
Dockerfile
Normal file
@@ -0,0 +1,97 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
#
|
||||
# neuronetz-gateway — multi-stage image.
|
||||
#
|
||||
# builder stage : installs dependencies into a self-contained virtualenv using uv.
|
||||
# runtime stage : copies the venv + source, drops to a NON-ROOT user, contains
|
||||
# no build tools, and runs `python -m neuronetz_gateway`.
|
||||
#
|
||||
# uv is pulled from the official distroless image so we don't need network access
|
||||
# to `pip install uv`. Dependencies come from pyproject.toml (+ uv.lock if present).
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Stage 1 — builder
|
||||
# ----------------------------------------------------------------------------
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
# Bring in the `uv` binary from its official image.
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
||||
|
||||
ENV UV_LINK_MODE=copy \
|
||||
UV_COMPILE_BYTECODE=1 \
|
||||
UV_PYTHON_DOWNLOADS=never \
|
||||
# Create the project venv at a stable, copyable location.
|
||||
VIRTUAL_ENV=/opt/venv \
|
||||
PATH=/opt/venv/bin:$PATH
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Create the target virtualenv up front so uv installs into it.
|
||||
RUN uv venv /opt/venv
|
||||
|
||||
# Dependency layer: copy only the manifest(s) first for better caching.
|
||||
# uv.lock is optional in Phase 1 — the wildcard makes COPY succeed either way.
|
||||
COPY pyproject.toml ./
|
||||
COPY uv.loc[k] ./
|
||||
|
||||
# Install dependencies. If a lockfile is present `uv sync` honours it; otherwise
|
||||
# we fall back to resolving straight from pyproject.toml. Either way the build
|
||||
# does NOT fail when the lock is absent.
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
if [ -f uv.lock ]; then \
|
||||
uv sync --frozen --no-install-project --no-dev ; \
|
||||
else \
|
||||
uv pip install --python /opt/venv/bin/python -r pyproject.toml ; \
|
||||
fi
|
||||
|
||||
# Now copy the application source and install the project itself into the venv.
|
||||
# README.md + LICENSE are required by the build backend (pyproject `readme`/license).
|
||||
COPY README.md LICENSE ./
|
||||
COPY src ./src
|
||||
COPY alembi[c] ./alembic
|
||||
COPY alembic.in[i] ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --python /opt/venv/bin/python --no-deps .
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Stage 2 — runtime
|
||||
# ----------------------------------------------------------------------------
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
# Runtime-only OS packages: curl is used by the compose healthcheck.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Non-root user.
|
||||
RUN groupadd --system --gid 10001 gateway \
|
||||
&& useradd --system --uid 10001 --gid gateway --home-dir /app --shell /usr/sbin/nologin gateway
|
||||
|
||||
ENV VIRTUAL_ENV=/opt/venv \
|
||||
PATH=/opt/venv/bin:$PATH \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
GATEWAY_BIND_HOST=0.0.0.0 \
|
||||
GATEWAY_BIND_PORT=8080
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the fully-populated virtualenv and the application from the builder.
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
COPY --from=builder /app/src ./src
|
||||
# alembic assets are optional during early scaffolding; copy if present.
|
||||
COPY --from=builder /app/alembi[c] ./alembic
|
||||
COPY --from=builder /app/alembic.in[i] ./
|
||||
|
||||
# Drop privileges. No build tools are present in this stage.
|
||||
USER gateway
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# Liveness probe target lives at /healthz (see SPEC §6.4).
|
||||
HEALTHCHECK --interval=15s --timeout=3s --start-period=20s --retries=5 \
|
||||
CMD curl -fsS "http://127.0.0.1:${GATEWAY_BIND_PORT}/healthz" || exit 1
|
||||
|
||||
# Default command: run the server. Compose overrides this in dev to run
|
||||
# `alembic upgrade head` first (see docker-compose.dev.yml).
|
||||
CMD ["python", "-m", "neuronetz_gateway"]
|
||||
Reference in New Issue
Block a user