Skip to content

feat(deploy): write deployment platform ADR and deploy public app #82

Description

@natashaannn

User story

As a team member, I want the public-facing tools (carousel generator, YouTube captions extractor, landing page) deployed to a free hosting platform, so that external users can access them without running the app locally.

As a developer making future changes, I want a recorded architecture decision explaining why the platform was chosen, so that the tradeoffs are understood and the decision can be revisited if requirements change.

Background

The following routes are ready to deploy publicly once Issues #76, #77, #78, #79, and #80 are merged:

  • / — landing page
  • /about — about page
  • /carousel — carousel generator (subscriber-gated)
  • /get-youtube-captions — YouTube captions extractor (subscriber-gated)

The critical deployment constraint is Puppeteer. The carousel generator (/api/generate-carousel, /api/extract-captions) launches a headless Chromium browser via Puppeteer. This rules out standard serverless platforms (Vercel, Netlify) unless the carousel generation is offloaded to a separate worker service. A container host running Dockerfile.public (see below) is the path of least resistance.

The existing Dockerfile must not be used for public deployment. It includes Python 3.12, PyTorch, whisperx, pyannote, ffmpeg, and other ML dependencies needed only for the internal video pipeline. Issue #83 is restructuring that Dockerfile as the NVIDIA/Linux internal pipeline image. This issue creates a completely separate Dockerfile.public — a slim Node.js + Chromium image with no ML stack — specifically for the public deployment. The two Dockerfiles are independent artifacts serving different purposes.

An ADR must be written and decided before any deployment work begins.

Acceptance criteria

ADR

Given the ADR has been written
When a developer reads docs/adr/ADR-001-deployment-platform.md
Then they understand the evaluated options, the decision drivers, the chosen platform, and the tradeoffs accepted

Deployment

Given the ADR decision has been made and the platform is configured
When a user visits the public domain
Then the landing page loads and the carousel and captions tools are accessible behind the subscriber email gate

Given the deployment is live
When a user submits a carousel generation request
Then it completes successfully (Puppeteer runs and returns base64 images)

Given the deployment is live
When the RESEND_API_KEY, SUBSCRIBER_COOKIE_SECRET, RESEND_FROM_EMAIL, RESEND_SEGMENT_ID, and RESEND_TOPICS env vars are set in the platform dashboard
Then the subscriber gate works end-to-end

Slim Dockerfile

Given Dockerfile.public is created
When it is built with docker build -f Dockerfile.public .
Then the image contains Node.js and Chromium but NOT Python, PyTorch, ffmpeg, or any ML model dependencies

Out of scope

Technical context

Public feature runtime requirements

Feature Requires
Landing, about Node.js, Next.js SSR
/carousel, /get-youtube-captions Node.js, Next.js SSR
/api/generate-carousel Node.js, Chromium (Puppeteer), Sharp
/api/extract-captions, /api/transcribe Node.js, outbound HTTP (YouTube)
/api/auth/subscribe Node.js, outbound HTTP (Resend)

Minimum container spec: ~512 MB RAM (Puppeteer peak usage), 1 shared CPU, persistent (not serverless).

Platforms to evaluate in the ADR

Platform Docker support Puppeteer Free tier Cold starts Notes
Fly.io ✅ (3 shared VMs, 256 MB each) None May need to bump to 512 MB; worldwide edge
Railway ✅ ($5/month credit) None Good DX; credit may run out
Render ✅ (web service, spins down after 15 min) Yes (cold start on first request) Free tier has 15-min sleep
Google Cloud Run ✅ (2M req/month, 400K GB-sec) Configurable min-instances Generous free tier; min-instances=0 causes cold starts
Koyeb ✅ (2 nano instances, 512 MB) None Less established
Vercel (free) N/A Serverless; Puppeteer requires separate worker — significant extra work
Netlify (free) N/A Same constraint as Vercel

Environment variables required on the platform

From .env.example (after Issue #80):

RESEND_API_KEY
RESEND_FROM_EMAIL
RESEND_SEGMENT_ID
RESEND_TOPICS
SUBSCRIBER_COOKIE_SECRET

Do NOT set ANTHROPIC_API_KEY or INTERNAL_API_SECRET on the public deployment. Auto-carousel (the only feature requiring ANTHROPIC_API_KEY) is removed in Issue #81.

Dockerfile.public

Write Dockerfile.public from scratch as a minimal image — do not derive it from the internal Dockerfile, which is being restructured by Issue #83 for the NVIDIA/Linux pipeline path. The only shared content is the Chromium system library block (apt-get dependencies for headless Chrome), which can be copied but is stable and well-understood.

Dockerfile.public must include:

  • Node.js 20.x
  • Chromium + Puppeteer system libraries: chromium libasound2 libatk-bridge2.0-0 libatspi2.0-0 libcups2 libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1 libxfixes3 libxkbcommon0 libxrandr2
  • PUPPETEER_SKIP_DOWNLOAD=true and PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
  • npm install + next build

Dockerfile.public must NOT include:

  • Python or any pip dependencies
  • PyTorch, whisperx, pyannote, faster-whisper, rembg
  • ffmpeg
  • Any Remotion or video pipeline dependencies

Implementation details

  1. Write ADR at docs/adr/ADR-001-deployment-platform.md using the format:

    • Title, Status (Proposed → Accepted), Date
    • Context (Puppeteer constraint, free tier preference, single-container requirement)
    • Decision drivers (free tier, no cold starts on carousel, Puppeteer support, DX)
    • Considered options (table above with pros/cons)
    • Decision (chosen platform + rationale)
    • Consequences (what gets easier, what gets harder, what to revisit if traffic grows)
  2. Create Dockerfile.public — slim Node.js + Chromium image written from scratch (see spec above)

  3. Configure platform — create app/service on chosen platform; point to Dockerfile.public; set all required env vars from the platform dashboard

  4. Verify deployment — smoke test: load landing page, submit an email on /carousel, generate a carousel, download an image

  5. Update README.md or create docs/DEPLOYMENT.md with the platform URL, how to redeploy, and where env vars are managed

Additional test scenarios

  • Carousel generation completes within the platform's request timeout (verify timeout is ≥ 60 s; Puppeteer can take 30–45 s on a cold container)
  • Subscriber email gate works end-to-end on the live domain (email → Resend contact created → cookie set → tool accessible)
  • /editor and /camera routes are inaccessible on the public deployment (they write to local filesystem — they will 500 or 404 without INTERNAL_API_SECRET, which is acceptable)

Hard constraints

  • Platform must be free tier (no credit card required beyond signup, or has a perpetual free tier)
  • Puppeteer must run server-side without a separate external browser service
  • INTERNAL_API_SECRET and ANTHROPIC_API_KEY must NOT be set in the public deployment environment
  • The ADR must be written and committed before any platform configuration begins
  • Dockerfile.public must be written independently of the internal Dockerfile — do not import or extend it

Dependency issues

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions