# ============================================================================= # VideoPress — Dockerfile # ============================================================================= # # Build: # docker build -t videopress . # # Run (quick): # docker run -d \ # -p 8080:8080 \ # -v /your/video/path:/media \ # --name videopress \ # videopress # # See docker-compose.yml for a fully-configured example. # ============================================================================= # ── Stage 1: Python dependency builder ────────────────────────────────────── FROM python:3.12-slim AS builder WORKDIR /build # Install build tools needed to compile gevent's C extensions RUN apt-get update && apt-get install -y --no-install-recommends \ gcc \ libffi-dev \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt . # Install into an isolated prefix so we can copy just the packages RUN pip install --upgrade pip \ && pip install --prefix=/install --no-cache-dir -r requirements.txt # ── Stage 2: Runtime image ─────────────────────────────────────────────────── FROM python:3.12-slim # ── System packages: ffmpeg (includes ffprobe) ─────────────────────────────── RUN apt-get update && apt-get install -y --no-install-recommends \ ffmpeg \ && rm -rf /var/lib/apt/lists/* # ── Copy Python packages from builder ──────────────────────────────────────── COPY --from=builder /install /usr/local # ── Create a non-root application user ─────────────────────────────────────── # UID/GID 1000 is conventional and matches most Linux desktop users, # which makes the volume-mapped files readable without extra chown steps. RUN groupadd --gid 1000 appuser \ && useradd --uid 1000 --gid 1000 --no-create-home --shell /sbin/nologin appuser # ── Application code ───────────────────────────────────────────────────────── WORKDIR /app COPY app/ app/ COPY wsgi.py run.py gunicorn.conf.py requirements.txt ./ COPY templates/ templates/ COPY static/ static/ # ── Media volume mount point ────────────────────────────────────────────────── # The host directory containing videos is mounted here at runtime. # All file-system access by the application is restricted to this path. RUN mkdir -p /media && chown appuser:appuser /media # ── Data directory for the SQLite settings database ────────────────────────── # Mounted as a named volume in docker-compose so settings survive restarts. RUN mkdir -p /data && chown appuser:appuser /data # ── File ownership ──────────────────────────────────────────────────────────── RUN chown -R appuser:appuser /app # ── Switch to non-root user ─────────────────────────────────────────────────── USER appuser # ── Environment defaults (all overridable via docker run -e or compose) ────── # MEDIA_ROOT — the path inside the container where videos are accessed. # Must match the container-side of your volume mount. # PORT — TCP port Gunicorn listens on (exposed below). # LOG_LEVEL — Gunicorn log verbosity (debug | info | warning | error). ENV MEDIA_ROOT=/media \ DB_PATH=/data/videopress.db \ PORT=8080 \ LOG_LEVEL=info \ PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 # ── Expose the web UI port ──────────────────────────────────────────────────── # The UI and API share the same port — no separate API port is needed. EXPOSE 8080 # ── Health check ───────────────────────────────────────────────────────────── HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ CMD python3 -c \ "import urllib.request; urllib.request.urlopen('http://localhost:8080/')" \ || exit 1 # ── Start Gunicorn ──────────────────────────────────────────────────────────── CMD ["gunicorn", "-c", "gunicorn.conf.py", "wsgi:application"]