Skip to content

feat(media): raise the upload cap to 90 MiB and stop buffering uploads in RAM - #123

Merged
ssavutu merged 1 commit into
mainfrom
media/raise-upload-size-limit
Jul 31, 2026
Merged

feat(media): raise the upload cap to 90 MiB and stop buffering uploads in RAM#123
ssavutu merged 1 commit into
mainfrom
media/raise-upload-size-limit

Conversation

@ssavutu

@ssavutu ssavutu commented Jul 31, 2026

Copy link
Copy Markdown
Member

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.jpg at 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. ParseMultipartForm was being handed maxBytes, but that argument is the in-memory threshold, not the cap — MaxBytesReader has 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.Copy to a temp file, image.DecodeConfig reads headers only), so a large upload costs container disk, not memory.

Nginx client_max_body_size 26m → 91m, plus client_body_timeout / proxy_read_timeout / proxy_send_timeout at 300s so a 90 MiB upload over a slow uplink isn't killed mid-flight.

MEDIA_MAX_UPLOAD_BYTES is 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

  • 413 on an oversize body.
  • A body 4× the memory threshold still parses — proves the spill path works and the cap doesn't fire early. This is the regression that would catch a future re-coupling of the two limits.
  • maxUploadBytes default / 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:

  1. Reinstall deploy/nginx/triangle-cms.conf on Delta, nginx -t, reload. The installed copy is known to drift from the repo.
  2. Recreate the backend slots so they pick up MEDIA_MAX_UPLOAD_BYTES.

Delta today still shows client_max_body_size 26m and no MEDIA_MAX_UPLOAD_BYTES in the running containers.

🤖 Generated with Claude Code

…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>
@ssavutu
ssavutu merged commit c54d2c0 into main Jul 31, 2026
6 checks passed
@ssavutu
ssavutu deleted the media/raise-upload-size-limit branch July 31, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant