From 711f0e44115484c369a917cbc0b63adc2c8d6b79 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 12 Jul 2026 14:56:09 +0200 Subject: [PATCH] docker: 3-binary deployment (presentation, worker, playout) --- compose.traefik.yml | 26 ++++++++++---- compose.yml | 77 +++++++++++++++++++++++++---------------- k-tv-backend/Dockerfile | 45 ++++++++++++------------ 3 files changed, 90 insertions(+), 58 deletions(-) diff --git a/compose.traefik.yml b/compose.traefik.yml index e6a372a..1a7f736 100644 --- a/compose.traefik.yml +++ b/compose.traefik.yml @@ -11,24 +11,38 @@ # TRAEFIK_CERT_RESOLVER cert resolver name for TLS (default: letsencrypt) # FRONTEND_HOST public hostname for the frontend e.g. tv.example.com # BACKEND_HOST public hostname for the backend API e.g. tv-api.example.com +# PLAYOUT_HOST public hostname for playout streams e.g. tv-playout.example.com # # Remember: NEXT_PUBLIC_API_URL in .env must be the *public* backend URL, # e.g. https://tv-api.example.com/api/v1, and you must rebuild after changing it. services: - backend: - ports: [] # Traefik handles ingress; no direct port exposure needed + presentation: + ports: [] networks: - default - traefik labels: - "traefik.enable=true" - "traefik.docker.network=${TRAEFIK_NETWORK:-traefik_proxy}" - - "traefik.http.routers.ktv-backend.rule=Host(`${BACKEND_HOST}`)" - - "traefik.http.routers.ktv-backend.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.ktv-backend.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}" - - "traefik.http.services.ktv-backend.loadbalancer.server.port=3000" + - "traefik.http.routers.ktv-presentation.rule=Host(`${BACKEND_HOST}`)" + - "traefik.http.routers.ktv-presentation.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" + - "traefik.http.routers.ktv-presentation.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}" + - "traefik.http.services.ktv-presentation.loadbalancer.server.port=3000" + + playout: + ports: [] + networks: + - default + - traefik + labels: + - "traefik.enable=true" + - "traefik.docker.network=${TRAEFIK_NETWORK:-traefik_proxy}" + - "traefik.http.routers.ktv-playout.rule=Host(`${PLAYOUT_HOST}`)" + - "traefik.http.routers.ktv-playout.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" + - "traefik.http.routers.ktv-playout.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}" + - "traefik.http.services.ktv-playout.loadbalancer.server.port=9090" frontend: ports: [] diff --git a/compose.yml b/compose.yml index f3948c8..3196bd2 100644 --- a/compose.yml +++ b/compose.yml @@ -1,19 +1,19 @@ services: - # ── Backend (Rust / Axum) ────────────────────────────────────────────────── - backend: - build: ./k-tv-backend + # ── Presentation (Rust / Axum — HTTP API) ──────────────────────────────── + presentation: + build: + context: ./k-tv-backend + target: presentation + image: registry.gabrielkaszewski.dev/k-tv-presentation:latest ports: - "${BACKEND_PORT:-3000}:3000" environment: - HOST=0.0.0.0 - PORT=3000 - DATABASE_URL=sqlite:///app/data/k-tv.db?mode=rwc - # Allow requests from the browser (the user-facing frontend URL) - CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS} - # Auth — generate with: openssl rand -hex 32 - JWT_SECRET=${JWT_SECRET} - # Cookie secret — generate with: openssl rand -base64 64 - COOKIE_SECRET=${COOKIE_SECRET} - JWT_EXPIRY_HOURS=${JWT_EXPIRY_HOURS:-24} - SECURE_COOKIE=${SECURE_COOKIE:-false} @@ -21,7 +21,6 @@ services: - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true} - DB_MAX_CONNECTIONS=${DB_MAX_CONNECTIONS:-5} - DB_MIN_CONNECTIONS=${DB_MIN_CONNECTIONS:-1} - # Jellyfin — all three required for schedule generation - JELLYFIN_BASE_URL=${JELLYFIN_BASE_URL} - JELLYFIN_API_KEY=${JELLYFIN_API_KEY} - JELLYFIN_USER_ID=${JELLYFIN_USER_ID} @@ -34,40 +33,60 @@ services: timeout: 5s retries: 3 + # ── Worker (background jobs) ───────────────────────────────────────────── + worker: + build: + context: ./k-tv-backend + target: worker + image: registry.gabrielkaszewski.dev/k-tv-worker:latest + environment: + - DATABASE_URL=sqlite:///app/data/k-tv.db?mode=rwc + - JELLYFIN_BASE_URL=${JELLYFIN_BASE_URL} + - JELLYFIN_API_KEY=${JELLYFIN_API_KEY} + - JELLYFIN_USER_ID=${JELLYFIN_USER_ID} + volumes: + - backend_data:/app/data + depends_on: + presentation: + condition: service_healthy + restart: unless-stopped + + # ── Playout (HLS streaming) ────────────────────────────────────────────── + playout: + build: + context: ./k-tv-backend + target: playout + image: registry.gabrielkaszewski.dev/k-tv-playout:latest + ports: + - "${PLAYOUT_PORT:-9090}:9090" + environment: + - DATABASE_URL=sqlite:///app/data/k-tv.db?mode=rwc + - PLAYOUT_LISTEN_ADDR=0.0.0.0:9090 + - PLAYOUT_STORAGE_PATH=/tmp/k-tv-playout + - PLAYOUT_SEGMENT_DURATION=${PLAYOUT_SEGMENT_DURATION:-6} + volumes: + - backend_data:/app/data + depends_on: + presentation: + condition: service_healthy + restart: unless-stopped + # ── Frontend (Next.js) ──────────────────────────────────────────────────── frontend: build: context: ./k-tv-frontend args: - # Browser-visible backend URL — baked into the client bundle at build time. - # Rebuild the image after changing this. NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:4000/api/v1} + NEXT_PUBLIC_PLAYOUT_URL: ${NEXT_PUBLIC_PLAYOUT_URL:-http://localhost:9090} + image: registry.gabrielkaszewski.dev/k-tv-frontend:latest ports: - "${FRONTEND_PORT:-3001}:3001" environment: - # Server-side API URL — uses Docker's internal network, never exposed. - # Next.js API routes (e.g. /api/stream/[channelId]) use this. - API_URL: http://backend:3000/api/v1 + API_URL: http://presentation:3000/api/v1 depends_on: - backend: + presentation: condition: service_healthy restart: unless-stopped volumes: backend_data: - - # ── Optional: PostgreSQL ─────────────────────────────────────────────────── - # Uncomment the db service and set DATABASE_URL in backend's environment: - # DATABASE_URL: postgres://ktv:${POSTGRES_PASSWORD}@db:5432/ktv - # - # db: - # image: postgres:16-alpine - # environment: - # POSTGRES_USER: ktv - # POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - # POSTGRES_DB: ktv - # volumes: - # - db_data:/var/lib/postgresql/data - # restart: unless-stopped - # - # db_data: diff --git a/k-tv-backend/Dockerfile b/k-tv-backend/Dockerfile index 444d98f..0494c11 100644 --- a/k-tv-backend/Dockerfile +++ b/k-tv-backend/Dockerfile @@ -1,31 +1,30 @@ FROM rust:1.92 AS builder - WORKDIR /app COPY . . +RUN cargo build --release -p presentation -p worker -p playout -# Build the release binary -RUN cargo build --release -p api - -FROM debian:bookworm-slim - +# Presentation image +FROM debian:bookworm-slim AS presentation WORKDIR /app - -# Install OpenSSL, CA certs, and ffmpeg (provides ffprobe for local-files duration scanning) -RUN apt-get update && apt-get install -y --no-install-recommends \ - libssl3 \ - ca-certificates \ - ffmpeg \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=builder /app/target/release/api . - - -# Create data directory for SQLite +RUN apt-get update && apt-get install -y --no-install-recommends libssl3 ca-certificates && rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/target/release/k-tv . RUN mkdir -p /app/data - -ENV DATABASE_URL=sqlite:///app/data/template.db -ENV SESSION_SECRET=supersecretchangeinproduction - EXPOSE 3000 +CMD ["./k-tv"] -CMD ["./api"] +# Worker image +FROM debian:bookworm-slim AS worker +WORKDIR /app +RUN apt-get update && apt-get install -y --no-install-recommends libssl3 ca-certificates ffmpeg && rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/target/release/k-tv-worker . +RUN mkdir -p /app/data +CMD ["./k-tv-worker"] + +# Playout image +FROM debian:bookworm-slim AS playout +WORKDIR /app +RUN apt-get update && apt-get install -y --no-install-recommends libssl3 ca-certificates ffmpeg && rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/target/release/k-tv-playout . +RUN mkdir -p /app/data /tmp/k-tv-playout +EXPOSE 9090 +CMD ["./k-tv-playout"]