Standalone Media uploads get their own tusd instance, separate from Drive's
ADR-0006 gave Drive resumable uploads via a tusd sidecar, with a single webhook (DriveTusWebhookView) writing files under the drive/ prefix. The Studio (frontend/apps/stream/, see ADR-0011) needs the same resumability for Standalone Media video uploads (up to 2GB, per apps.media.validators), but its completion side does something structurally different from Drive's: it must create/update a Media row and kick off the whole transcode/transcribe/summarize pipeline (enqueue_media_processing), not just index a file.
We decided to run a second, dedicated tusd instance for media uploads rather than routing them through Drive's existing one.
Considered Options
- Presigned direct-to-S3 upload (mirrors
apps.headless.views.MediaPresignedUploadView, today gated behindRequiresCommunity): no extra process to run, but a single presigned PUT has no resume — a dropped connection on a multi-GB video restarts from zero. Rejected for the same reason ADR-0006 rejected it for Drive. - Reuse Drive's tusd instance, branching in a shared webhook by upload metadata (tusd forwards custom metadata in its hook payload): cheapest infra-wise, but forces one webhook handler to understand two unrelated domains (
DriveFileindexing vs. the entire media pipeline dispatch) — the branch would only grow messier as either side changes independently. - Second dedicated tusd instance (chosen): its own
docker-compose.ymlservice, its own port, its own webhook secret, POSTing to a new, single-purpose webhook view that creates/updates a standaloneMediarow and callsenqueue_media_processing.
Why this was chosen
- Drive's webhook and Media's webhook do fundamentally different things on completion — one indexes a file, the other dispatches a multi-stage async pipeline. Keeping them as separate
tusdinstances keeps each webhook single-purpose, same reasoningapps.media/apps.streamingalready follow as separate apps (ADR-0009) rather than overloading one. tusdis cheap to run a second time — no meaningful operational cost beyond one more container and one more shared secret to manage, which is small next to the alternative of a permanently branching webhook.- Keeps Drive's upload contract (
DRIVE_TUS_WEBHOOK_SECRET,drive/prefix) completely untouched — zero risk of a media-upload change breaking Drive.
Consequences
- New
docker-compose.ymlservice (e.g.tusd-media), new settings (MEDIA_TUS_WEBHOOK_SECRET,MEDIA_TUS_PUBLIC_URL), new webhook view + route, mirroringapps.drive.views.TusCompleteWebhookView's shape but creating a standaloneMedia(owner =request.user,community=None) instead of aDriveFile. - Two
tusdprocesses to run and monitor in every environment (localmake dev, and whatever the production compose/orchestration setup is) instead of one. - The new webhook must validate uploaded file extension/size the same way
MediaUploadSerializer/MediaPresignedUploadViewdo today (apps.media.validators.validate_media_filename/validate_media_size) — tusd itself doesn't enforce the project's media-type rules.