Files
ersatztv/docker/ci/Dockerfile
T
timothy 07048b8c96
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Failing after 13s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 15s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 16s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 15s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
Build CI Toolchain Image / Build & push CI image (push) Successful in 29s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m7s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m11s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m6s
ci(390): address cold review — pin-drift guard, honest cron, doc fixes
Cold adversarial review findings:
- MEDIUM (Renovate generates pin drift): add a blocking ci-image-pin job. Renovate
  manages docker/ci/Dockerfile's base pins but cannot bump an opaque :<sha> in
  container.image, so a base bump would publish a new image, test the OLD one, and
  merge with the Dockerfile disagreeing with the pin. The guard fails when the pin
  isn't the last commit to touch docker/ci, or when the 5 jobs pin different tags —
  making the documented two-step enforced rather than remembered.
- MEDIUM (cron was a no-op): the weekly rebuild updated nothing (jobs pin :<sha>) and
  buildcache would have restored the apt layer verbatim, collecting none of the base
  updates it existed for. Added no-cache on the schedule path and rewrote the comment
  to state what it actually is: a build canary + a fresh :latest for the next bump.
- LOW: FFMPEG_TAG was referenced in the Dockerfile + docs but never existed (the FROM
  is hardcoded); reworded both.
- LOW: paths: filtered the exact file while the docs claimed docker/ci/** — use **.
- NIT: docs oversold ENTRYPOINT reset as a gotcha; act overrides it anyway. Marked
  defensive.

Refs #390
2026-07-17 09:34:46 +02:00

101 lines
5.2 KiB
Docker

# Shared CI toolchain image (ersatztv#390) — .NET 10 SDK + Node 22 + prod-identical ffmpeg.
#
# Built/pushed by .gitea/workflows/ci-image.yml to 192.168.1.95:3000/timothy/ersatztv-ci.
# The toolchain jobs in docker-build.yml run *inside* this image via `container:`, so they
# install nothing at run time: no setup-dotnet, no setup-node, no `apt-get install ffmpeg`,
# no `dotnet tool install`. Project dependencies (NuGet/npm) are NOT baked in — those change
# per commit and stay on actions/cache.
#
# Layering rationale: we start from our OWN ffmpeg base and copy the .NET SDK on top, which is
# exactly the pattern docker/Dockerfile uses for the prod image (`FROM ersatztv-ffmpeg` +
# `COPY --from=dotnet-runtime /usr/share/dotnet`). That base is ghcr.io/linuxserver/baseimage-ubuntu:noble,
# the same Ubuntu release as mcr.microsoft.com/dotnet/sdk:10.0-noble, so the copied SDK matches
# the base's glibc/ICU. It also gives CI the *prod* ffmpeg build rather than a generic apt one —
# the fidelity that the ersatztv#299 seeded-media/scanner E2E follow-ups will need.
#
# Keep the pins below in sync with what CI actually needs; Renovate tracks the image pins
# (see renovate.json). The two dotnet tool versions mirror what docker-build.yml used to
# `dotnet tool install` per run — bump them here, not in the workflow.
# Keep the ffmpeg tag on the FROM below equal to the one docker/Dockerfile pins, so CI's ffmpeg
# stays prod-identical. Renovate manages both pins (dockerfile manager); mirror any bump here.
FROM --platform=linux/amd64 192.168.1.95:3000/timothy/ersatztv-ffmpeg:8.1.2 AS ci-base
# The ffmpeg base sets ENTRYPOINT ["ffmpeg"] / CMD ["--help"] because it ships as an ffmpeg CLI.
# This image is a CI *job container*: the runner starts it long-lived and execs steps into it,
# so an ffmpeg entrypoint is wrong here. Reset both.
ENTRYPOINT []
CMD ["/bin/bash"]
ENV DEBIAN_FRONTEND=noninteractive
# Toolchain the workflow steps and the actions themselves need:
# git — actions/checkout
# ca-certificates, curl — NodeSource setup + general fetches
# tar, zstd, unzip, xz — actions/cache archive round-trip
# python3 — scripts/generate-endpoint-index.py (the api-docs job)
# jq — ad-hoc JSON in CI steps
# The ffmpeg base is a runtime image, so none of these are guaranteed present.
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
jq \
python3 \
tar \
unzip \
xz-utils \
zstd && \
apt-get clean -y && \
rm -rf /var/lib/apt/lists/*
# Node 22 (NodeSource) — matches the `node-version: '22.x'` the workflow used to request from
# setup-node, and the node:22 base docker/Dockerfile builds the SPA with. act_runner also needs a
# `node` on PATH inside the job container to execute JavaScript actions (actions/checkout, cache).
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y --no-install-recommends nodejs && \
apt-get clean -y && \
rm -rf /var/lib/apt/lists/*
# actions/checkout clones as root into a runner-mounted workspace, which trips git's
# "detected dubious ownership in repository" guard and breaks every `git` call in a step
# (scripts/e2e-local.sh's `git rev-parse --show-toplevel`, the base-ref diffs, ...). Trusting all
# paths is the right call for a throwaway single-tenant CI container.
RUN git config --global --add safe.directory '*'
# .NET 10 SDK. Copied from the official SDK image rather than dotnet-install.sh: same mechanism as
# docker/Dockerfile's runtime copy, one pinned artifact, no install script to fail at build time.
# global.json pins sdk 10.0.0 with rollForward latestMinor, which this satisfies.
COPY --from=mcr.microsoft.com/dotnet/sdk:10.0-noble-amd64 /usr/share/dotnet /usr/share/dotnet
ENV DOTNET_ROOT=/usr/share/dotnet \
PATH="/usr/share/dotnet:/root/.dotnet/tools:${PATH}" \
DOTNET_CLI_TELEMETRY_OPTOUT=1 \
DOTNET_NOLOGO=1 \
DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
# Global dotnet tools the jobs used to install per run (5s + ~4s each, every run, forever).
# dotnet-ef — the migrations job (was: `dotnet tool install --global dotnet-ef --version 9.0.12`)
# dotnet-reportgenerator-globaltool — the test job's coverage summary
# Baked here so the versions live in one tracked place instead of inline workflow strings.
RUN dotnet tool install --global dotnet-ef --version 9.0.12 && \
dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.5.10
# Build-time smoke test: fail the IMAGE build, not a CI run, if any part of the toolchain is
# unusable. Notably `dotnet --info` catches a missing ICU/glibc dependency in the base — the one
# real risk of copying the SDK onto a non-Microsoft base.
RUN set -eux; \
dotnet --info; \
dotnet ef --version; \
node --version; \
npm --version; \
git --version; \
python3 --version; \
ffmpeg -version; \
ffprobe -version; \
# reportgenerator has no clean version probe: `--version` prints the banner and then exits 1
# ("No report files specified"), so asserting on it would fail this build. Probe that the
# dotnet-tool shim resolves on PATH instead.
command -v reportgenerator