Follow-up to #33 / ADR-033 (PR for feat/33-audio-upload-transcript).
Question. Uploads are validated by probing (ffprobe: accept an audio stream, reject a video stream, read the duration) and converted with ffmpeg to 16 kHz mono PCM. The runtime image is gcr.io/distroless/static-debian12:nonroot and contains neither. TAPIR_UPLOADS_ENABLED is off by default for exactly this reason: the feature cannot work in production until this is decided. The Dockerfile is deliberately unchanged in the PR.
Options
A. Copy static ffmpeg + ffprobe binaries into the distroless image (recommended). Only option that satisfies "accept m4a/mp3/opus/mp4, validate duration, reject video". Cost: a larger image (~70 MB) and a CVE surface to patch. Keeps no shell / no package manager.
B. Accept WAV only, client converts — fails "I just want to upload it".
C. A transcode sidecar — a second deployable, against ADR-018's reasoning.
(Pure-Go decoders cover wav/mp3 only, not m4a/opus/mp4.)
Blocks: the Dockerfile change and enabling uploads. Not the code, which uses ports and fakes.
If A: pin the ffmpeg version and verify its checksum in the build; note the adversarial-review finding on hostile containers (HLS/concat playlists) in the PR before choosing a version. Verified on koala 2026-10-01 with static ffmpeg 7.0.2 (md5 checked against the publisher's file, which proves integrity not authenticity).
Follow-up to #33 / ADR-033 (PR for `feat/33-audio-upload-transcript`).
**Question.** Uploads are validated by *probing* (`ffprobe`: accept an audio stream, reject a video stream, read the duration) and converted with `ffmpeg` to 16 kHz mono PCM. The runtime image is `gcr.io/distroless/static-debian12:nonroot` and contains neither. `TAPIR_UPLOADS_ENABLED` is off by default for exactly this reason: **the feature cannot work in production until this is decided.** The Dockerfile is deliberately unchanged in the PR.
**Options**
- **A. Copy static `ffmpeg` + `ffprobe` binaries into the distroless image (recommended).** Only option that satisfies "accept m4a/mp3/opus/mp4, validate duration, reject video". Cost: a larger image (~70 MB) and a CVE surface to patch. Keeps no shell / no package manager.
- B. Accept WAV only, client converts — fails "I just want to upload it".
- C. A transcode sidecar — a second deployable, against ADR-018's reasoning.
- (Pure-Go decoders cover wav/mp3 only, not m4a/opus/mp4.)
**Blocks:** the Dockerfile change and enabling uploads. Not the code, which uses ports and fakes.
**If A:** pin the ffmpeg version and verify its checksum in the build; note the adversarial-review finding on hostile containers (HLS/concat playlists) in the PR before choosing a version. Verified on koala 2026-10-01 with static ffmpeg 7.0.2 (md5 checked against the publisher's file, which proves integrity not authenticity).
Refs: ADR-033 Open items, #33.
---
_Created via git-mcp on behalf of @mathias_
Decision (Mathias, 2026-10-02): option A, static ffmpeg + ffprobe in the distroless image. Implementation follows in PR #39 once the adversarial review of it has reported; this closes on merge. Facts checked while preparing it, two of which differ from the issue text:
Size. The issue estimated about 70 MB. Measured: ffmpeg is 79.8 MB and ffprobe 79.7 MB, so the image grows by about 160 MB, roughly 2.3x what was estimated. If that matters, the lever is to stop needing ffprobe (probe with ffmpeg -i), which is a code change and not made.
Licence.ffmpeg -version shows --enable-gpl --enable-version3: the build is GPLv3. Fine for a private service you run yourself; the licence text is copied into the image. Not checked: whether this matters if the image is ever distributed.
Pin. ffmpeg 7.0.2 amd64, tarball sha256 abda8d77ce8309141f83ab8edf0596834087c52467f6badf376a6a2a4c87cf67; the publisher's md5 matched the download. The checksum proves the bytes are the ones reviewed, not that the publisher is authentic (nothing is signed).
Durability. Upstream's releases/ path holds only the current version, so a newer release moves 7.0.2 to old-releases/ (the build tries both) or removes it. The durable fix is vendoring the verified tarball; not done.
Not tested here: the image build itself (no docker on this host). The download, checksum, extraction and a probe/convert of a synthetic video were dry-run with the same shell steps; CI builds the image on the PR.
Uploads stay off until TAPIR_UPLOADS_ENABLED is set in the deployment. Spool volume, ingress body limit and the other plumbing are #37.
**Decision (Mathias, 2026-10-02): option A, static ffmpeg + ffprobe in the distroless image.** Implementation follows in PR #39 once the adversarial review of it has reported; this closes on merge. Facts checked while preparing it, two of which differ from the issue text:
- **Size.** The issue estimated about 70 MB. Measured: `ffmpeg` is 79.8 MB and `ffprobe` 79.7 MB, so the image grows by about **160 MB**, roughly 2.3x what was estimated. If that matters, the lever is to stop needing `ffprobe` (probe with `ffmpeg -i`), which is a code change and not made.
- **Licence.** `ffmpeg -version` shows `--enable-gpl --enable-version3`: the build is **GPLv3**. Fine for a private service you run yourself; the licence text is copied into the image. Not checked: whether this matters if the image is ever distributed.
- **Pin.** ffmpeg 7.0.2 amd64, tarball sha256 `abda8d77ce8309141f83ab8edf0596834087c52467f6badf376a6a2a4c87cf67`; the publisher's md5 matched the download. The checksum proves the bytes are the ones reviewed, not that the publisher is authentic (nothing is signed).
- **Durability.** Upstream's `releases/` path holds only the current version, so a newer release moves 7.0.2 to `old-releases/` (the build tries both) or removes it. The durable fix is vendoring the verified tarball; not done.
- **Not tested here:** the image build itself (no docker on this host). The download, checksum, extraction and a probe/convert of a synthetic video were dry-run with the same shell steps; CI builds the image on the PR.
- Uploads stay off until `TAPIR_UPLOADS_ENABLED` is set in the deployment. Spool volume, ingress body limit and the other plumbing are #37.
---
_Created via git-mcp on behalf of @mathias_
Correction to my earlier comment here. I wrote that CI builds the image on the PR. It does not: the image job has if: github.event_name != 'pull_request'. I then built the Dockerfile myself with buildah on koala. The first build failed (tar: xz: Cannot exec -- the golang:1.26 build image has no xz), which would have broken the image build on the merge to main. Fixed in PR #39 (71cd50a: xz-utils installed in the ffmpeg stage). After the fix: builds, 180 MB total image, ffmpeg 7.0.2 and ffprobe 7.0.2 run as nonroot in distroless, TAPIR_FFMPEG_BIN / TAPIR_FFPROBE_BIN set. The sha256 check passed in the real build.
Still true: the first CI build of this Dockerfile happens on the merge to main. The size figure above supersedes my earlier "about 160 MB added" only in the sense that the whole image is 180 MB.
**Correction to my earlier comment here.** I wrote that CI builds the image on the PR. It does not: the image job has `if: github.event_name != 'pull_request'`. I then built the Dockerfile myself with buildah on koala. **The first build failed** (`tar: xz: Cannot exec` -- the `golang:1.26` build image has no `xz`), which would have broken the image build on the merge to main. Fixed in PR #39 (`71cd50a`: `xz-utils` installed in the ffmpeg stage). After the fix: builds, **180 MB** total image, `ffmpeg 7.0.2` and `ffprobe 7.0.2` run as `nonroot` in distroless, `TAPIR_FFMPEG_BIN` / `TAPIR_FFPROBE_BIN` set. The sha256 check passed in the real build.
Still true: the first CI build of this Dockerfile happens on the merge to main. The size figure above supersedes my earlier "about 160 MB added" only in the sense that the whole image is 180 MB.
---
_Created via git-mcp on behalf of @mathias_
Resolved and verified live (2026-10-03). The first CI build of the Dockerfile on main passed (Gitea runs 8853 main push and 8854 tag v0.31.0, both success), CD bumped infra to 4a20670 (855d445), and the tapir pod is 1/1 Ready on localhost:5000/tapir:4a20670 with 0 restarts. Image carries static ffmpeg/ffprobe 7.0.2, sha256-pinned, built and inspected earlier by hand (180 MB, runs as nonroot in distroless). Enabling uploads in production is tracked in infra#520. Closing.
Resolved and verified live (2026-10-03). The first CI build of the Dockerfile on main passed (Gitea runs 8853 main push and 8854 tag `v0.31.0`, both `success`), CD bumped infra to `4a20670` (`855d445`), and the tapir pod is `1/1 Ready` on `localhost:5000/tapir:4a20670` with 0 restarts. Image carries static ffmpeg/ffprobe 7.0.2, sha256-pinned, built and inspected earlier by hand (180 MB, runs as nonroot in distroless). Enabling uploads in production is tracked in infra#520. Closing.
---
_Created via git-mcp on behalf of @mathias_
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Follow-up to #33 / ADR-033 (PR for
feat/33-audio-upload-transcript).Question. Uploads are validated by probing (
ffprobe: accept an audio stream, reject a video stream, read the duration) and converted withffmpegto 16 kHz mono PCM. The runtime image isgcr.io/distroless/static-debian12:nonrootand contains neither.TAPIR_UPLOADS_ENABLEDis off by default for exactly this reason: the feature cannot work in production until this is decided. The Dockerfile is deliberately unchanged in the PR.Options
ffmpeg+ffprobebinaries into the distroless image (recommended). Only option that satisfies "accept m4a/mp3/opus/mp4, validate duration, reject video". Cost: a larger image (~70 MB) and a CVE surface to patch. Keeps no shell / no package manager.Blocks: the Dockerfile change and enabling uploads. Not the code, which uses ports and fakes.
If A: pin the ffmpeg version and verify its checksum in the build; note the adversarial-review finding on hostile containers (HLS/concat playlists) in the PR before choosing a version. Verified on koala 2026-10-01 with static ffmpeg 7.0.2 (md5 checked against the publisher's file, which proves integrity not authenticity).
Refs: ADR-033 Open items, #33.
Created via git-mcp on behalf of @mathias
Decision (Mathias, 2026-10-02): option A, static ffmpeg + ffprobe in the distroless image. Implementation follows in PR #39 once the adversarial review of it has reported; this closes on merge. Facts checked while preparing it, two of which differ from the issue text:
ffmpegis 79.8 MB andffprobe79.7 MB, so the image grows by about 160 MB, roughly 2.3x what was estimated. If that matters, the lever is to stop needingffprobe(probe withffmpeg -i), which is a code change and not made.ffmpeg -versionshows--enable-gpl --enable-version3: the build is GPLv3. Fine for a private service you run yourself; the licence text is copied into the image. Not checked: whether this matters if the image is ever distributed.abda8d77ce8309141f83ab8edf0596834087c52467f6badf376a6a2a4c87cf67; the publisher's md5 matched the download. The checksum proves the bytes are the ones reviewed, not that the publisher is authentic (nothing is signed).releases/path holds only the current version, so a newer release moves 7.0.2 toold-releases/(the build tries both) or removes it. The durable fix is vendoring the verified tarball; not done.TAPIR_UPLOADS_ENABLEDis set in the deployment. Spool volume, ingress body limit and the other plumbing are #37.Created via git-mcp on behalf of @mathias
Correction to my earlier comment here. I wrote that CI builds the image on the PR. It does not: the image job has
if: github.event_name != 'pull_request'. I then built the Dockerfile myself with buildah on koala. The first build failed (tar: xz: Cannot exec-- thegolang:1.26build image has noxz), which would have broken the image build on the merge to main. Fixed in PR #39 (71cd50a:xz-utilsinstalled in the ffmpeg stage). After the fix: builds, 180 MB total image,ffmpeg 7.0.2andffprobe 7.0.2run asnonrootin distroless,TAPIR_FFMPEG_BIN/TAPIR_FFPROBE_BINset. The sha256 check passed in the real build.Still true: the first CI build of this Dockerfile happens on the merge to main. The size figure above supersedes my earlier "about 160 MB added" only in the sense that the whole image is 180 MB.
Created via git-mcp on behalf of @mathias
Resolved and verified live (2026-10-03). The first CI build of the Dockerfile on main passed (Gitea runs 8853 main push and 8854 tag
v0.31.0, bothsuccess), CD bumped infra to4a20670(855d445), and the tapir pod is1/1 Readyonlocalhost:5000/tapir:4a20670with 0 restarts. Image carries static ffmpeg/ffprobe 7.0.2, sha256-pinned, built and inspected earlier by hand (180 MB, runs as nonroot in distroless). Enabling uploads in production is tracked in infra#520. Closing.Created via git-mcp on behalf of @mathias