feat(media): raise the upload cap to 90 MiB and stop buffering uploads in RAM - #123
Merged
Conversation
…s in RAM The migrated WordPress corpus contains unresized camera originals up to ~77 MiB (largest: 2025/07/BZ9A5771.jpg), so the 25 MiB cap rejected files the newsroom demonstrably produces. Raise the default to 90 MiB, chosen to sit under Cloudflare's 100 MB request-body limit on the tunnel fronting Delta -- past that a body clears Nginx and the backend but dies at the edge with an error the CMS never sees. ParseMultipartForm was being handed the size limit as its argument, but that argument is the in-memory buffer threshold, not the cap (MaxBytesReader has always been what enforces the cap). At 90 MiB that would let a single upload pin its full size in RAM, plus the ~10 MiB of slack Go adds on top. Give it a fixed 8 MiB instead and let the rest spill to temp files; the write path already streams and only reads image headers for dimensions, so a large upload now costs container disk rather than memory. Nginx's client_max_body_size moves to 91m to stay just above the backend, and the body/proxy timeouts go to 300s so a 90 MiB upload over a slow uplink is not killed mid-flight. MEDIA_MAX_UPLOAD_BYTES is also plumbed through Compose and the env template -- it was read by the backend but never passed in, so the cap could previously only be changed by editing Go. Deploying this needs both halves: reinstall the Nginx site and reload, and recreate the backend slots. The smaller of the two limits silently wins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The migrated WordPress corpus contains unresized camera originals well past the current 25 MiB cap — the largest is
wp-content/uploads/2025/07/BZ9A5771.jpgat 76.3 MiB, with a dozen more in the 45–70 MiB range. Uploading files the newsroom already produces was a guaranteed 413.What changed
Cap raised to 90 MiB (
defaultMaxUploadBytes). The number is bounded above by Cloudflare's 100 MB request-body limit on the tunnel fronting Delta: a body that clears Nginx and the backend but exceeds that fails at the edge with an error the CMS never sees. Don't raise the pair past ~95 MiB without moving media uploads off the tunnel.Uploads no longer buffer in RAM.
ParseMultipartFormwas being handedmaxBytes, but that argument is the in-memory threshold, not the cap —MaxBytesReaderhas always been what enforces the cap. At 90 MiB that would have pinned a full upload's size in memory, plus the ~10 MiB of slack Go adds. It now gets a fixed 8 MiB and spills the rest to temp files. The write path already streams (io.Copyto a temp file,image.DecodeConfigreads headers only), so a large upload costs container disk, not memory.Nginx
client_max_body_size26m → 91m, plusclient_body_timeout/proxy_read_timeout/proxy_send_timeoutat 300s so a 90 MiB upload over a slow uplink isn't killed mid-flight.MEDIA_MAX_UPLOAD_BYTESis now plumbed through Compose and the env template. The backend read it but nothing passed it in, so the cap could previously only be changed by editing Go.Tests
maxUploadBytesdefault / env override / garbage fallback, with an assertion that the default stays ≥ 80 MiB so the corpus doesn't silently outgrow it again.Full server suite passes.
Deploy note
Both halves must land together or the smaller limit silently wins:
deploy/nginx/triangle-cms.confon Delta,nginx -t, reload. The installed copy is known to drift from the repo.MEDIA_MAX_UPLOAD_BYTES.Delta today still shows
client_max_body_size 26mand noMEDIA_MAX_UPLOAD_BYTESin the running containers.🤖 Generated with Claude Code