docker: fail-closed rebuild, ffmpeg checksum, smaller runtime

- Replace the mtime-touch stub-rebuild hack with cargo clean -p (a
  future-dated host file could silently ship the stub binary)
- Optional FFMPEG_SHA256 build arg verified before extraction
- Drop the redundant libssl3/libcrypto copies and the root
  supplementary group; strip the release binary; add a webhook-mode
  healthcheck to the compose example
This commit is contained in:
2026-08-08 20:34:21 +08:00
parent 9910da2914
commit c40b074b3c
4 changed files with 26 additions and 10 deletions
+4
View File
@@ -1,3 +1,7 @@
[workspace] [workspace]
members = ["crates/x-media", "crates/xmedia-bot"] members = ["crates/x-media", "crates/xmedia-bot"]
resolver = "3" resolver = "3"
# Smaller production binary; debug symbols are not shipped anyway.
[profile.release]
strip = true
+14 -9
View File
@@ -11,6 +11,11 @@ ARG APP_NAME=telegram-twitter-media-bot
# runners. `/redirect/latest/` floats to the newest release build; each build # runners. `/redirect/latest/` floats to the newest release build; each build
# also ships a .sha256. Swap `amd64` for `arm64` when building arm64 images. # also ships a .sha256. Swap `amd64` for `arm64` when building arm64 images.
ARG FFMPEG_URL=https://ffmpeg.martin-riedl.de/redirect/latest/linux/amd64/release/ffmpeg.zip ARG FFMPEG_URL=https://ffmpeg.martin-riedl.de/redirect/latest/linux/amd64/release/ffmpeg.zip
# Optional sha256 of ffmpeg.zip (pinned releases only): set to verify the
# download. The mirror publishes .sha256 sidecars next to pinned builds, e.g.
# https://ffmpeg.martin-riedl.de/download/linux/amd64/<id>_9.0/ffmpeg.zip.sha256
# (the /redirect/latest/ URL itself has no sidecar — pin the effective URL).
ARG FFMPEG_SHA256=
WORKDIR /build WORKDIR /build
@@ -30,19 +35,20 @@ RUN mkdir -p crates/x-media/src crates/xmedia-bot/src \
# root. `unzip -t` verifies the archive before extraction so a bad # root. `unzip -t` verifies the archive before extraction so a bad
# download fails loudly here instead of a cryptic later error. # download fails loudly here instead of a cryptic later error.
RUN wget -q -O /tmp/ffmpeg.zip "$FFMPEG_URL" \ RUN wget -q -O /tmp/ffmpeg.zip "$FFMPEG_URL" \
&& if [ -n "$FFMPEG_SHA256" ]; then echo "$FFMPEG_SHA256 /tmp/ffmpeg.zip" | sha256sum -c -; fi \
&& unzip -tq /tmp/ffmpeg.zip \ && unzip -tq /tmp/ffmpeg.zip \
&& unzip -q /tmp/ffmpeg.zip -d /usr/local/bin \ && unzip -q /tmp/ffmpeg.zip -d /usr/local/bin \
&& chmod +x /usr/local/bin/ffmpeg \ && chmod +x /usr/local/bin/ffmpeg \
&& rm /tmp/ffmpeg.zip \ && rm /tmp/ffmpeg.zip \
&& /usr/local/bin/ffmpeg -version >/dev/null && /usr/local/bin/ffmpeg -version >/dev/null
# 3. Real sources last: only our crates recompile on source changes. The # 3. Real sources last: only our crates recompile on source changes.
# COPY preserves host mtimes, which predate the stub artifacts from step 1; # `cargo clean -p` drops the two crates' artifacts while keeping the
# cargo's mtime-based freshness check would otherwise treat the stub build # compiled dependency layer, forcing a deterministic rebuild of the real
# as up-to-date and never compile the real sources. `touch` forces cargo to # sources. (The previous `touch`-mtimes hack silently shipped the stub
# see the real files as newer. # binary when host files carried future timestamps.)
COPY crates/ ./crates/ COPY crates/ ./crates/
RUN find crates -type f -name '*.rs' -exec touch {} + \ RUN cargo clean -p xmedia-bot -p x-media \
&& cargo build --release -p xmedia-bot && cargo build --release -p xmedia-bot
# ---------- runtime stage ---------- # ---------- runtime stage ----------
@@ -56,10 +62,9 @@ LABEL org.opencontainers.image.title="${APP_NAME}"
# Everything is copied in — no apt in the runtime stage. Privilege dropping is # Everything is copied in — no apt in the runtime stage. Privilege dropping is
# done by docker-entrypoint.sh with setpriv (util-linux, already in # done by docker-entrypoint.sh with setpriv (util-linux, already in
# bookworm-slim), so no gosu needed. # bookworm-slim), so no gosu needed. (libssl3/libcrypto are already in
# bookworm-slim; only ca-certificates and ffmpeg need copying.)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/lib/x86_64-linux-gnu/libssl.so.3* /usr/lib/x86_64-linux-gnu/
COPY --from=builder /usr/lib/x86_64-linux-gnu/libcrypto.so.3* /usr/lib/x86_64-linux-gnu/
COPY --from=builder /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg COPY --from=builder /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
WORKDIR /app WORKDIR /app
+8
View File
@@ -55,6 +55,14 @@ services:
depends_on: depends_on:
- nginx-proxy - nginx-proxy
container_name: tgxmb container_name: tgxmb
# Webhook mode only: the bot listens on WEBHOOK_PORT; nginx-proxy shows
# 502s while this is down, so surface it to the orchestrator.
healthcheck:
test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8443'"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
volumes: volumes:
certs: certs:
-1
View File
@@ -16,7 +16,6 @@ then
else else
usermod -u ${USER_ID} -o user > /dev/null 2>&1 || true usermod -u ${USER_ID} -o user > /dev/null 2>&1 || true
fi fi
usermod -a -G root user > /dev/null 2>&1 || true
# Bind-mounted volumes may not support chown; a failure here must not kill # Bind-mounted volumes may not support chown; a failure here must not kill
# the container either. # the container either.
chown -R `id -u user`:`id -u user` /app > /dev/null 2>&1 || true chown -R `id -u user`:`id -u user` /app > /dev/null 2>&1 || true