refactor: slim image with proper multi-stage build and --no-dev deps
- Dockerfile: separate runtime stage from build stage so g++, python3.12-dev, curl, gnupg are excluded from the final image - uv sync --no-dev: drop ruff/pytest from production image - Remove build.sh (replaced by CI) and root-level upload_tracker.py (stale duplicate) - .gitignore: add *.log Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# Custom
|
# Custom
|
||||||
frigate_train/
|
frigate_train/
|
||||||
|
*.log
|
||||||
runs/
|
runs/
|
||||||
yolov9c.pt
|
yolov9c.pt
|
||||||
.insightface/
|
.insightface/
|
||||||
|
|||||||
+23
-7
@@ -16,14 +16,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
gnupg \
|
gnupg \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
software-properties-common \
|
software-properties-common \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
python3.12 python3.12-venv python3.12-dev \
|
python3.12 python3.12-venv python3.12-dev \
|
||||||
libgl1 libglib2.0-0 libxext6 g++ tini \
|
libgl1 libglib2.0-0 libxext6 g++ \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& ln -sf /usr/bin/python3.12 /usr/bin/python3
|
&& ln -sf /usr/bin/python3.12 /usr/bin/python3
|
||||||
|
|
||||||
@@ -33,7 +30,7 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY pyproject.toml uv.lock ./
|
COPY pyproject.toml uv.lock ./
|
||||||
RUN uv sync --frozen \
|
RUN uv sync --frozen --no-dev \
|
||||||
&& uv cache clean
|
&& uv cache clean
|
||||||
|
|
||||||
COPY if_curator/ if_curator/
|
COPY if_curator/ if_curator/
|
||||||
@@ -41,7 +38,27 @@ COPY entrypoint.sh scheduler.py ./
|
|||||||
RUN chmod +x /app/entrypoint.sh
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
# ── Runtime stage ─────────────────────────────────────────────────────────
|
# ── Runtime stage ─────────────────────────────────────────────────────────
|
||||||
FROM build AS runtime
|
# Starts fresh from the base image — excludes build tools (g++,
|
||||||
|
# python3.12-dev, curl, gnupg) that are not needed at runtime.
|
||||||
|
|
||||||
|
FROM base-${TARGETARCH} AS runtime
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
software-properties-common \
|
||||||
|
tini \
|
||||||
|
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
||||||
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
python3.12 python3.12-venv \
|
||||||
|
libgl1 libglib2.0-0 libxext6 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& ln -sf /usr/bin/python3.12 /usr/bin/python3
|
||||||
|
|
||||||
|
# Copy app (with .venv) and uv from build stage
|
||||||
|
COPY --from=build /app /app
|
||||||
|
COPY --from=build /usr/local/bin/uv /usr/local/bin/uv
|
||||||
|
|
||||||
# Expose CUDA/cuDNN libraries from pip packages so onnxruntime-gpu
|
# Expose CUDA/cuDNN libraries from pip packages so onnxruntime-gpu
|
||||||
# can find libcublasLt.so.12 and libcudnn.so.9 at runtime
|
# can find libcublasLt.so.12 and libcudnn.so.9 at runtime
|
||||||
@@ -56,4 +73,3 @@ ENV HF_HOME=/models/huggingface INSIGHTFACE_HOME=/models
|
|||||||
|
|
||||||
HEALTHCHECK CMD test -f /app/entrypoint.sh || exit 1
|
HEALTHCHECK CMD test -f /app/entrypoint.sh || exit 1
|
||||||
ENTRYPOINT ["tini", "--", "/app/entrypoint.sh"]
|
ENTRYPOINT ["tini", "--", "/app/entrypoint.sh"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# --- Configuration ---
|
|
||||||
COMMIT_MSG="automated update: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
|
|
||||||
# --- Git Sync ---
|
|
||||||
echo "💾 Committing and pushing changes to GitHub..."
|
|
||||||
|
|
||||||
git add .
|
|
||||||
|
|
||||||
# Only proceed if there are actual changes to commit
|
|
||||||
if ! git diff-index --quiet HEAD --; then
|
|
||||||
git commit -m "$COMMIT_MSG"
|
|
||||||
git push
|
|
||||||
echo "✅ Changes pushed. GitHub Actions will now build and deploy the image."
|
|
||||||
else
|
|
||||||
echo "ℹ️ No changes detected; nothing to push."
|
|
||||||
fi
|
|
||||||
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
"""Persistent tracker for Immich asset IDs already uploaded to Frigate.
|
|
||||||
|
|
||||||
Prevents duplicate uploads across runs by recording each successfully
|
|
||||||
uploaded asset ID in a JSON file within the configured CACHE_DIR.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
UPLOAD_TRACKER_FILE = "frigate_uploaded_ids.json"
|
|
||||||
|
|
||||||
|
|
||||||
def _tracker_path() -> Path:
|
|
||||||
"""Return path to the tracker file, using Config.CACHE_DIR if available."""
|
|
||||||
try:
|
|
||||||
from .config import Config
|
|
||||||
|
|
||||||
return Path(Config.CACHE_DIR) / UPLOAD_TRACKER_FILE
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
return Path(UPLOAD_TRACKER_FILE)
|
|
||||||
|
|
||||||
|
|
||||||
def load_uploaded_ids() -> set[str]:
|
|
||||||
"""Load the set of Immich asset IDs already uploaded to Frigate."""
|
|
||||||
path = _tracker_path()
|
|
||||||
if not path.exists():
|
|
||||||
return set()
|
|
||||||
try:
|
|
||||||
with open(path) as f:
|
|
||||||
data = json.load(f)
|
|
||||||
return set(data.get("uploaded_asset_ids", []))
|
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
|
||||||
logger.warning(f"Could not load upload tracker: {e}")
|
|
||||||
return set()
|
|
||||||
|
|
||||||
|
|
||||||
def save_uploaded_ids(uploaded_ids: set[str]) -> None:
|
|
||||||
"""Persist the set of uploaded Immich asset IDs to disk."""
|
|
||||||
path = _tracker_path()
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(path, "w") as f:
|
|
||||||
json.dump({"uploaded_asset_ids": sorted(uploaded_ids)}, f, indent=2)
|
|
||||||
|
|
||||||
|
|
||||||
def mark_uploaded(asset_id: str) -> None:
|
|
||||||
"""Mark a single Immich asset ID as uploaded to Frigate."""
|
|
||||||
ids = load_uploaded_ids()
|
|
||||||
ids.add(asset_id)
|
|
||||||
save_uploaded_ids(ids)
|
|
||||||
logger.debug(f"Marked asset {asset_id} as uploaded to Frigate")
|
|
||||||
|
|
||||||
|
|
||||||
def is_uploaded(asset_id: str) -> bool:
|
|
||||||
"""Check if an Immich asset ID has already been uploaded to Frigate."""
|
|
||||||
return asset_id in load_uploaded_ids()
|
|
||||||
|
|
||||||
|
|
||||||
def filter_already_uploaded(asset_ids: list[str]) -> list[str]:
|
|
||||||
"""Return only asset IDs that have NOT yet been uploaded to Frigate.
|
|
||||||
|
|
||||||
Logs how many were skipped so the user knows dedup is working.
|
|
||||||
"""
|
|
||||||
uploaded = load_uploaded_ids()
|
|
||||||
new_ids = [aid for aid in asset_ids if aid not in uploaded]
|
|
||||||
skipped = len(asset_ids) - len(new_ids)
|
|
||||||
if skipped:
|
|
||||||
logger.info(f"Skipping {skipped} assets already uploaded to Frigate")
|
|
||||||
return new_ids
|
|
||||||
|
|
||||||
|
|
||||||
def reset_uploaded_ids() -> None:
|
|
||||||
"""Clear the tracker — useful for re-training from scratch."""
|
|
||||||
save_uploaded_ids(set())
|
|
||||||
logger.info("Cleared Frigate upload tracker")
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user