screentinker/Dockerfile
Rob K 7896cccf4d Install ffmpeg in the Docker image and document it as a requirement
The runtime image never had ffmpeg, so every Docker deployment silently
lost video thumbnails and durations; the README never mentioned it for
bare-metal installs either.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131RYmVh8ePEhparD3mXBhU
2026-08-07 09:30:32 +01:00

56 lines
2.8 KiB
Docker

# ScreenTinker server image: serves the dashboard, the web player, and the
# device API. All mutable state (db, uploads, jwt secret) lives under /data so it
# survives container restarts - mount a volume there. A built ScreenTinker.apk
# can be mounted at /data/ScreenTinker.apk to enable OTA APK downloads.
#
# No TLS in the image: it listens on plain HTTP :3001. Front it with a
# TLS-terminating reverse proxy / Cloudflare in production.
# --- builder: install production deps (native: better-sqlite3, sharp) ---
FROM node:20-slim AS builder
WORKDIR /app/server
# build toolchain in case a native prebuild is missing for the target arch
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 build-essential \
&& rm -rf /var/lib/apt/lists/*
COPY server/package.json server/package-lock.json ./
RUN npm ci --omit=dev
# --- runtime ---
FROM node:20-slim
# ffmpeg (ships ffprobe) powers video thumbnails + duration extraction at upload.
# Without it videos still upload and play, but arrive with no thumbnail or duration.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_ENV=production
# Relocate all state onto the volume (config.js reads DATA_DIR; unset would use
# the in-repo paths, which we do not want in a container).
ENV DATA_DIR=/data
WORKDIR /app/server
# App source (node_modules/test/db/uploads/certs are excluded via .dockerignore),
# then the built deps, the frontend the server serves, and the VERSION file it
# reads as ../VERSION.
COPY server/ /app/server/
COPY --from=builder /app/server/node_modules /app/server/node_modules
COPY frontend/ /app/frontend/
# shared/Transitions is a RUNTIME dependency: server/lib/transition-config.js + transition-bundle.js
# require the shader manifest/params/sources from ../../shared at load time (the server won't boot
# without it). Small, and keeps the .glsl files the single source across server + player + Tizen.
COPY shared/ /app/shared/
COPY VERSION /app/VERSION
# the /openapi.yaml route serves ../docs/openapi.yaml (the spec Redoc on /docs fetches);
# without this it 404s in the image even though it serves fine from a dev checkout.
COPY docs/openapi.yaml /app/docs/openapi.yaml
# database.js requires scripts/migrate-multitenancy at boot
COPY scripts/ /app/scripts/
# The BrightSign bridge and sync modules are served to the player from ../brightsign so the copy
# the player loads can never drift from the one on the player's own storage. That RUNTIME path
# does not exist unless the directory is in the image: without this the routes 404 in a container
# while working perfectly from a dev checkout — and a missing player asset fails silently, because
# the SPA fallback answers 200 with HTML where JavaScript was expected.
COPY brightsign/ /app/brightsign/
VOLUME ["/data"]
EXPOSE 3001
CMD ["node", "server.js"]