Add Docker image + Gitea CI for Virgo Dev deploy
Containerize Hold Slayer as a single image (one FastAPI process serving REST/WS/MCP and its built SvelteKit dashboard) for deployment to Virgo Dev on triton. - Dockerfile: 3-stage (node builds the dashboard → python wheels → runtime). Runs from source via `pip install -e .` so db/database.py resolves alembic.ini (via __file__.parent.parent) and migrations run on boot. The loose top-level modules (main.py, config.py) also require the source layout. pjsua2 is left unbuilt (documented stub media) — fine for a mock-SIP deploy. - .dockerignore: keep .env and gitignored dashboard build artifacts out of the context; the node stage builds a fresh dashboard. - CI (.gitea/workflows): single-image Trivy scan + build + push to git.helu.ca/r/hold-slayer (sha / latest-on-main / semver tags), mirroring the Demeter workflow. Requires a PACKAGE_TOKEN Actions secret. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
53
Dockerfile
Normal file
53
Dockerfile
Normal file
@@ -0,0 +1,53 @@
|
||||
# Hold Slayer — single image: FastAPI process that also serves the built
|
||||
# SvelteKit dashboard at "/". One container, four surfaces (REST/WS/MCP/dash).
|
||||
#
|
||||
# pjsua2 is deliberately NOT built here — it is not pip-installable (compiled
|
||||
# from pjproject) and the media pipeline degrades to documented stub mode
|
||||
# without it. That is correct for a mock-SIP dev deploy (USE_MOCK_SIP=true);
|
||||
# /health honestly reports the mock engine as "degraded". Building real media
|
||||
# is a separate, deliberate piece of work.
|
||||
|
||||
# Stage 1: build the SvelteKit dashboard → dashboard/build/ (SPA, static).
|
||||
# dashboard/build and dashboard/node_modules are gitignored, so build fresh
|
||||
# here rather than copying a stale working-tree artifact.
|
||||
FROM node:22-alpine AS dashboard
|
||||
WORKDIR /dashboard
|
||||
COPY dashboard/package.json dashboard/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY dashboard/ ./
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: runtime. The app runs FROM SOURCE at /app (not purely from
|
||||
# site-packages): db/database.py locates alembic.ini via
|
||||
# Path(__file__).parent.parent, and main.py/config.py are loose top-level
|
||||
# modules — both require the source tree layout under the working dir. An
|
||||
# editable install puts the deps + entry points in place while keeping /app/db,
|
||||
# /app/config.py, /app/alembic.ini resolving to the real files.
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
|
||||
# build-essential: some deps compile from source (no manylinux wheel).
|
||||
# curl: required for the compose healthcheck (GET /health).
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends build-essential curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies first (better layer caching) using just the manifest,
|
||||
# then the source. -e keeps the package importable from /app so alembic.ini
|
||||
# and the loose modules resolve correctly at runtime.
|
||||
COPY pyproject.toml README.md ./
|
||||
COPY . .
|
||||
# Bring in the freshly built dashboard (overwrites any stale gitignored copy).
|
||||
COPY --from=dashboard /dashboard/build ./dashboard/build
|
||||
|
||||
RUN pip install --no-cache-dir -e . \
|
||||
&& apt-get purge -y build-essential && apt-get autoremove -y
|
||||
|
||||
EXPOSE 21081
|
||||
|
||||
# Migrations run in the app's own init_db() on boot (db/database.py), so no
|
||||
# separate `alembic upgrade` here. Bind host/port from the same env vars
|
||||
# pydantic-settings reads (HOST/PORT) so configured values and the actual bind
|
||||
# cannot drift. Deploy sets PORT=21081 (image default 8000 collides with other
|
||||
# host-net services on triton).
|
||||
CMD ["sh", "-c", "uvicorn main:app --host ${HOST:-0.0.0.0} --port ${PORT:-21081}"]
|
||||
Reference in New Issue
Block a user