deploy: containerize the live simple-mode API (Docker)
Ships the production serving path (FastAPI upload/map UI + statistical device-category identification) as a lean, reproducible container: - Dockerfile: python:3.10-slim, non-root user, /health HEALTHCHECK, serves uvicorn src.api.main_simple:app. Bakes in the joblib category model and the static rtl_433 protocol table; excludes the 11 GB test corpora and the benched Phase 3B CNN binaries. Built image is 417 MB. - requirements-prod.txt: pins matched to the versions that actually train/serve the model (scikit-learn 1.6.1 / numpy 2.2.6) so joblib.load() stays valid. torch/onnx intentionally absent — the CNN is benched, not on the serving path. - docker-compose.yml: single service, named volume seeds from baked data/ on first run then persists captures_simple.json writes across restarts. - .dockerignore: keeps data/, venv/, vendored firmware, docs out of the image. Validated: image builds, container reports healthy, and the statistical classifier loads + predicts inside the container. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# Keep the image small and reproducible. The 11 GB test corpora, the local
|
||||
# venv, and the vendored Flipper firmware are NOT needed to serve the app.
|
||||
|
||||
# Heavy / irrelevant
|
||||
data/rf_test_datasets/
|
||||
data/test_db/
|
||||
data/test_known_devices/
|
||||
data/test_rtl433_real/
|
||||
signatures/
|
||||
venv/
|
||||
logs/
|
||||
docs/
|
||||
tests/
|
||||
test_files/
|
||||
scripts/
|
||||
|
||||
# Benched Phase 3B CNN binaries (not on the serving path)
|
||||
models/category_cnn.pt
|
||||
models/category_cnn.onnx
|
||||
|
||||
# VCS / caches / editor
|
||||
.git/
|
||||
.gitignore
|
||||
**/__pycache__/
|
||||
**/*.pyc
|
||||
**/*.pyo
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
*.egg-info/
|
||||
.DS_Store
|
||||
|
||||
# Docs & planning (not needed at runtime)
|
||||
*.md
|
||||
CONTEXT.md
|
||||
FABLE.md
|
||||
PLAN_TO_PROD.md
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# GigLez — production image for the live "simple mode" API.
|
||||
# Serves: FastAPI upload/map UI + statistical device-category identification.
|
||||
FROM python:3.10-slim
|
||||
|
||||
# Faster, quieter, unbuffered logs in containers.
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PIP_NO_CACHE_DIR=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Dependencies first for layer caching — only re-runs when reqs change.
|
||||
COPY requirements-prod.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements-prod.txt
|
||||
|
||||
# Application code + assets the live path needs.
|
||||
COPY src/ ./src/
|
||||
COPY static/ ./static/
|
||||
COPY templates/ ./templates/
|
||||
COPY config/ ./config/
|
||||
COPY models/category_classifier.joblib ./models/category_classifier.joblib
|
||||
|
||||
# Seed data: the static rtl_433 protocol table (read-only) and an initial
|
||||
# capture store. When /app/data is mounted as a *named volume*, Docker seeds
|
||||
# the empty volume from these baked files on first run, then persists writes
|
||||
# to captures_simple.json across restarts.
|
||||
COPY data/rtl_433_protocols.json data/captures_simple.json data/weather_sensors_found.txt ./data/
|
||||
|
||||
# Run as a non-root user.
|
||||
RUN useradd --create-home --uid 10001 giglez \
|
||||
&& chown -R giglez:giglez /app
|
||||
USER giglez
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
# Container-native healthcheck hits the app's /health endpoint.
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=15s --retries=3 \
|
||||
CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/health').status==200 else 1)"
|
||||
|
||||
# Serve with uvicorn (module app object), not the __main__ dev banner.
|
||||
CMD ["uvicorn", "src.api.main_simple:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -0,0 +1,22 @@
|
||||
# GigLez — single-service deployment of the live simple-mode API.
|
||||
# docker compose up --build → http://localhost:8000
|
||||
services:
|
||||
giglez:
|
||||
build: .
|
||||
image: giglez:latest
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
# Persist the JSON capture store across restarts. Seeded from the
|
||||
# baked-in data/ on first run (see Dockerfile), then app writes survive.
|
||||
- giglez_data:/app/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/health').status==200 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
start_period: 15s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
giglez_data:
|
||||
@@ -0,0 +1,30 @@
|
||||
# GigLez production runtime — the LIVE simple-mode path only
|
||||
# (src/api/main_simple.py → SignatureMatcher → pattern_decoder → statistical ML).
|
||||
#
|
||||
# Versions are pinned to what actually trained/serves the model in this env.
|
||||
# In particular scikit-learn/numpy MUST match the versions the
|
||||
# models/category_classifier.joblib bundle was built with (1.6.1 / 2.2.x),
|
||||
# or joblib.load() will warn/break. Do NOT downgrade to the old
|
||||
# requirements.txt pins — those drive the dormant SQLAlchemy/PostGIS path.
|
||||
#
|
||||
# NOTE: torch/onnx are intentionally absent — the Phase 3B CNN is benched
|
||||
# (loses to the statistical model), so it is not part of the serving path.
|
||||
|
||||
# Web stack
|
||||
fastapi==0.121.1
|
||||
starlette==0.46.0
|
||||
uvicorn[standard]==0.31.1
|
||||
jinja2==3.1.6
|
||||
python-multipart==0.0.22
|
||||
pydantic==2.12.4
|
||||
|
||||
# Numerics + statistical ML (RAW category classifier)
|
||||
numpy==2.2.6
|
||||
scipy==1.15.3
|
||||
scikit-learn==1.6.1
|
||||
joblib==1.5.3
|
||||
|
||||
# Support
|
||||
loguru==0.7.2
|
||||
pyyaml==6.0.2
|
||||
python-dateutil==2.9.0.post0
|
||||
Reference in New Issue
Block a user