From dc29976c1ff7a8c52924ac868eb6e7c3ce94b8c8 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 11 Mar 2026 22:49:59 +0100 Subject: [PATCH] feat: add Docker configuration and environment setup for backend and frontend --- .env.example | 44 ++++++++++++++++++++++ compose.yml | 73 ++++++++++++++++++++++++++++++++++++ k-tv-frontend/.dockerignore | 4 ++ k-tv-frontend/Dockerfile | 46 +++++++++++++++++++++++ k-tv-frontend/next.config.ts | 2 +- 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 compose.yml create mode 100644 k-tv-frontend/.dockerignore create mode 100644 k-tv-frontend/Dockerfile diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..545e37d --- /dev/null +++ b/.env.example @@ -0,0 +1,44 @@ +# Copy this file to .env and fill in the values before running `docker compose up`. + +# ── Ports (optional, defaults shown) ───────────────────────────────────────── +BACKEND_PORT=3000 +FRONTEND_PORT=3001 + +# ── Auth ────────────────────────────────────────────────────────────────────── +# Generate: openssl rand -hex 32 +JWT_SECRET=change-me-generate-with-openssl-rand-hex-32 + +# Generate: openssl rand -base64 64 +COOKIE_SECRET=change-me-must-be-at-least-64-characters-long-for-production!! + +JWT_EXPIRY_HOURS=24 + +# Set to true when serving over HTTPS +SECURE_COOKIE=false +PRODUCTION=false + +# ── CORS ────────────────────────────────────────────────────────────────────── +# Origin(s) from which the browser will hit the backend, comma-separated. +# Must match what users type in their browser for the frontend. +# Example (local): http://localhost:3001 +# Example (remote): https://tv.example.com +CORS_ALLOWED_ORIGINS=http://localhost:3001 + +# ── Frontend / API URL ──────────────────────────────────────────────────────── +# Public URL of the BACKEND, as seen from the user's browser. +# This is baked into the Next.js client bundle at build time. +# Example (local): http://localhost:3000/api/v1 +# Example (remote): https://api.example.com/api/v1 +NEXT_PUBLIC_API_URL=http://localhost:3000/api/v1 + +# ── Jellyfin ────────────────────────────────────────────────────────────────── +JELLYFIN_BASE_URL=http://jellyfin:8096 +JELLYFIN_API_KEY=your-jellyfin-api-key-here +JELLYFIN_USER_ID=your-jellyfin-user-id-here + +# ── Database pool (optional) ────────────────────────────────────────────────── +DB_MAX_CONNECTIONS=5 +DB_MIN_CONNECTIONS=1 + +# ── PostgreSQL (optional, uncomment db service in compose.yml first) ────────── +# POSTGRES_PASSWORD=change-me diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..348e85a --- /dev/null +++ b/compose.yml @@ -0,0 +1,73 @@ +services: + + # ── Backend (Rust / Axum) ────────────────────────────────────────────────── + backend: + build: ./k-tv-backend + 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} + - PRODUCTION=${PRODUCTION:-false} + - 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} + volumes: + - backend_data:/app/data + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/api/v1/config || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + + # ── Frontend (Next.js) ──────────────────────────────────────────────────── + frontend: + build: + context: ./k-tv-frontend + args: + # Browser-visible backend URL — must be reachable from the user's browser. + # If running on a server: http://your-server-ip:3000/api/v1 + # Baked into the client bundle at build time; rebuild after changing. + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3000/api/v1} + 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 + depends_on: + backend: + 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-frontend/.dockerignore b/k-tv-frontend/.dockerignore new file mode 100644 index 0000000..f794f41 --- /dev/null +++ b/k-tv-frontend/.dockerignore @@ -0,0 +1,4 @@ +.next +node_modules +.env* +*.md diff --git a/k-tv-frontend/Dockerfile b/k-tv-frontend/Dockerfile new file mode 100644 index 0000000..a40e5bb --- /dev/null +++ b/k-tv-frontend/Dockerfile @@ -0,0 +1,46 @@ +# ── Stage 1: Install dependencies ──────────────────────────────────────────── +FROM oven/bun:1 AS deps + +WORKDIR /app + +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +# ── Stage 2: Build ──────────────────────────────────────────────────────────── +FROM oven/bun:1 AS builder + +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# NEXT_PUBLIC_* vars are baked into the client bundle at build time. +# Pass the public backend URL via --build-arg (see compose.yml). +ARG NEXT_PUBLIC_API_URL=http://localhost:3000/api/v1 +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN bun run build + +# ── Stage 3: Production runner ──────────────────────────────────────────────── +FROM node:22-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +# standalone output + static assets +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3001 +ENV PORT=3001 +ENV HOSTNAME=0.0.0.0 + +CMD ["node", "server.js"] diff --git a/k-tv-frontend/next.config.ts b/k-tv-frontend/next.config.ts index e9ffa30..68a6c64 100644 --- a/k-tv-frontend/next.config.ts +++ b/k-tv-frontend/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "standalone", }; export default nextConfig;