Skip to content

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 behind RequiresCommunity): 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 (DriveFile indexing 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.yml service, its own port, its own webhook secret, POSTing to a new, single-purpose webhook view that creates/updates a standalone Media row and calls enqueue_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 tusd instances keeps each webhook single-purpose, same reasoning apps.media/apps.streaming already follow as separate apps (ADR-0009) rather than overloading one.
  • tusd is 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.yml service (e.g. tusd-media), new settings (MEDIA_TUS_WEBHOOK_SECRET, MEDIA_TUS_PUBLIC_URL), new webhook view + route, mirroring apps.drive.views.TusCompleteWebhookView's shape but creating a standalone Media (owner = request.user, community=None) instead of a DriveFile.
  • Two tusd processes to run and monitor in every environment (local make 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/MediaPresignedUploadView do today (apps.media.validators.validate_media_filename/validate_media_size) — tusd itself doesn't enforce the project's media-type rules.

Strum — Documentação.