Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Deployment Guide

Last Updated: October 5th, 2026

This guide outlines how to deploy the web application stack using your preferred architecture. Choose the method that best fits your environment, whether you prefer building from source, utilizing pre-built container images via GitHub Packages (GHCR), or running natively without Docker.


Choose Your Deployment Approach

  1. Docker Compose (Recommended): The easiest way to run the entire multi-container stack. Ideal for most homelabbers and standard deployments.
  2. Standalone Docker CLI: Run individual docker run commands within a custom network. Best for users who want granular control over each container without Compose.
  3. Native / Bare Metal: Run the application directly on your host machine’s OS without containerization.

Prerequisites

Before you begin, ensure your host machine is prepared:


Method 1: Docker Compose Deployment

This method uses Docker Compose to spin up the Database, Backend, and Frontend simultaneously.

Option A: Build from Source (Local Repository)

Use this option if you have cloned the repository and want to build the images locally.

  1. Set up your environment variables:
cp .env.example .env

Open the .env file and customize the values (passwords, JWT secrets, etc.).

  1. Create media directories and assign permissions:
mkdir -p ./backend/media/hls
chmod -R 777 ./backend/media
  1. Build and start the stack:
docker compose up -d --build

tip

You can also use the provided local script: ./deploy-local.sh

Option B: Use Pre-Built Images (GHCR)

Use this option for a faster deployment using stable, pre-built images. Because you are not cloning the repository, you will need to create your own docker-compose.yml file.

  1. Create your environment file: Create an .env file in your directory and populate it with the required variables (see Reference).
  2. Create media directories and assign permissions:
mkdir -p ./backend/media/hls
chmod -R 777 ./backend/media
  1. Create the Docker Compose file: Create a file named docker-compose.yml and paste the following configuration:
services:
  backend:
    image: ghcr.io/flexible-output-view/fov-backend:main
    restart: unless-stopped
    volumes:
      - ./backend/media:/var/media
    ports:
      - "4000:4000" # API
      - "9999-10010:9999-10010"
      - "9999-10010:9999-10010/udp" # SRT
    networks:
      - frontend
      - backend
    depends_on:
      fov-db:
        condition: service_healthy

    environment:
      - PORT=4000
      - DB_HOST=fov-db
      - DB_PORT=5432
      - DB_USER=root
      - SRT_PORT=9999
      - DB_PASSWORD=${DB_PASSWORD}
      - DB_NAME=fovwebdb
      - FFMPEG_PATH=/usr/local/bin/ffmpeg
      - MEDIA_ROOT=/var/media
      - SRT_URL=${SRT_URL}
      - API_HOSTNAME=${API_URL}
      - TWITCH_ID=${TWITCH_ID}
      - TWITCH_SECRET=${TWITCH_SECRET}
      - JWT_SECRET=${JWT_SECRET}
      - JWT_EXPIRES_IN=${JWT_EXPIRES_IN}

  frontend:
    image: ghcr.io/flexible-output-view/fov-frontend:main
    restart: unless-stopped
    depends_on:
      backend:
        condition: service_started
    ports:
      - "4200:80"
    networks:
      - frontend
    environment:
      - API_URL=http://backend:4000

  fov-db:
    image: postgres:18-bookworm
    restart: unless-stopped
    volumes:
      - fov-db:/var/lib/postgresql
      - ./fovwebdb.sql:/docker-entrypoint-initdb.d/init.sql  # NOTE: Ensure you provide the initial sql file, or remove this mount if handling the database separately.
    networks:
      - backend

    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U root -d fovwebdb" ]
      interval: 10s
      timeout: 5s
      retries: 10

    environment:
      POSTGRES_DB: fovwebdb
      POSTGRES_USER: root
      POSTGRES_PASSWORD: ${DB_PASSWORD}

networks:
  frontend:
  backend:
    internal: true

volumes:
  fov-db:
  1. Download the initial database sql file and save it to fovwebdb.sql in the same directory as your docker compose.
  2. Pull images and start the stack:
docker compose pull
docker compose up -d

Local Endpoints Overview

Once the stack is running on default settings, your services are available at:

  • Frontend Web App: http://localhost:4200
  • API Backend: http://localhost:4000
  • Stream Ingest (SRT): Ports 9999-10010 (TCP/UDP)
  • Database: localhost:5432

warning

Default configurations bind services directly to your host machine. This is not recommended for public-facing production environments. See the Production Guide below.


Method 2: Standalone Docker CLI Deployment

If you prefer to manage containers manually without Docker Compose, follow these steps to isolate the stack on a custom bridge network.

1. Create a custom network:

docker network create fov-network

2. Deploy the Database: If using pre-built images, please download the initial database sql file

docker run -d \
  --name fov-db \
  --network fov-network \
  -v fov-db:/var/lib/postgresql \
  -v $(pwd)/fovwebdb.sql:/docker-entrypoint-initdb.d/init.sql \
  -e POSTGRES_DB=fovwebdb \
  -e POSTGRES_USER=root \
  -e POSTGRES_PASSWORD=your_secure_db_password_here \
  postgres:18-bookworm

3. Deploy the Backend: If using pre-built images, replace fov-backend with ghcr.io/flexible-output-view/fov-backend:main in the command below.

docker run -d \
  --name backend \
  --network fov-network \
  -p 4000:4000 \
  -p 9999-10010:9999-10010 \
  -p 9999-10010:9999-10010/udp \
  -v $(pwd)/backend/media:/var/media \
  -e PORT=4000 \
  -e DB_HOST=fov-db \
  -e DB_PORT=5432 \
  -e DB_USER=root \
  -e DB_PASSWORD=your_secure_db_password_here \
  -e DB_NAME=fovwebdb \
  -e FFMPEG_PATH=/usr/local/bin/ffmpeg \
  -e MEDIA_ROOT=/var/media \
  -e SRT_PORT=9999 \
  -e SRT_URL=localhost \
  -e API_HOSTNAME=http://localhost:4000/api \
  -e JWT_SECRET=your_jwt_secret_here \
  -e JWT_EXPIRES_IN=7d \
  fov-backend

4. Deploy the Frontend: If using pre-built images, replace fov-frontend with ghcr.io/flexible-output-view/fov-frontend:main in the command below.

docker run -d \
  --name frontend \
  --network fov-network \
  -p 4200:80 \
  -e API_URL=http://backend:4000 \
  fov-frontend

Method 3: Native / Bare Metal Deployment

To run the application directly on your host operating system (without Docker):

  • Backend Setup: Refer to the backend documentation for configuring a native Node.js and FFmpeg environment.
  • Frontend Setup: Refer to the frontend documentation for running the Angular application natively via Node.js and the Angular CLI.

Production Deployment (Nginx Proxy Manager)

For production environments, the application stack should sit behind a reverse proxy like Nginx Proxy Manager (NPM). This secures traffic over standard HTTP/HTTPS ports (80/443) and safely hides internal database and API ports from the public internet.

hardware & bandwidth considerations

High Network Bandwidth: Ingesting live video feeds and serving HLS chunks consumes massive inbound and outbound data.

Compute Limits: Every active live stream spawns a dedicated FFmpeg process. Provision your server’s CPU accordingly based on your expected concurrent streams.

1. Create a shared external network:

docker network create nginx-proxy-network

2. Deploy Nginx Proxy Manager: Deploy this NPM stack connected to the shared network:

services:
  app:
    image: 'jc21/nginx-proxy-manager:2.15.1'
    restart: unless-stopped
    ports:
      - '80:80'   # Public HTTP
      - '443:443' # Public HTTPS
      - '81:81'   # Admin Panel
    environment:
      TZ: "Europe/Paris"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - nginx-proxy-network

networks:
  nginx-proxy-network:
    external: true

3. Use the Production Override File (docker-compose.prod.yml): The docker-compose.prod.yml file restricts public ports to loopback/SRT streams and joins the nginx-proxy-network. You can customize it to your needs:

services:
  backend:
    ports: !override
      - "127.0.0.1:4001:4000"
      - "0.0.0.0:9999-10010:9999-10010"
      - "0.0.0.0:9999-10010:9999-10010/udp"
    networks:
      - nginx-proxy-network

  frontend:
    ports: !override
      - "127.0.0.1:4201:80"
    networks:
      - nginx-proxy-network

  fov-db:
    ports: !override
      - "127.0.0.1:5432:5432"

networks:
  nginx-proxy-network:
    external: true

4. Deploy the application with production overrides: Update your .env file with production domain names, then start the stack:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build

5. Configure Proxy Hosts in NPM: Log into NPM (port 81) and map your domains to the internal Docker containers:

  • Frontend Host: Forward to container frontend on port 80 (Enable Force SSL).
  • Backend API Host: Forward to container backend on port 4000 (Enable Force SSL).

(Note: Stream ingestion ports 9999-10010 bypass the proxy and remain publicly accessible for your SRT streaming sources).


Appendix: Architecture & Configuration Reference

Environment Variables Reference

These core variables must be defined in your .env file for the application to run.

VariableDescriptionDefault / Example
DB_PASSWORDSecure root password for PostgreSQL.your_secure_db_password_here
SRT_URLHostname or IP for SRT video streaming endpoints.localhost
API_URLPublicly accessible URL pointing to the backend API.http://localhost:4000/api
JWT_SECRETSecret key for generating JSON Web Tokens.your_jwt_secret_here
JWT_EXPIRES_INToken expiration timeframe.7d
TWITCH_ID(Deprecated soon) Twitch API client ID.your_twitch_id_here
TWITCH_SECRET(Deprecated soon) Twitch API client secret.your_twitch_secret_here

Docker Volumes & Storage

Volume / MountTarget ContainerPurpose
./backend/media:/var/mediaBackendStores persistent media assets and generated HLS video segments.
fov-db:/var/lib/postgresqlDatabaseNamed volume ensuring database records persist across restarts.
./fovwebdb.sql:/docker-entrypoint-initdb.d/init.sqlDatabaseSeeds the schema and initial data on first-time initialization.

Docker Networking

Network NameTypePurpose
frontendBridgeConnects the frontend to the backend API for web traffic.
backendInternal BridgeCompletely isolates the database so it is exclusively reachable by the backend.
nginx-proxy-networkExternal BridgeUsed to route traffic securely through an external reverse proxy (Production only).

Internal Container Variables (Hardcoded in Compose)

For users modifying the architecture, these are the internal routing variables configured within the Docker Compose files.

VariableTargetDefault ValueDescription
PORTBackend4000Internal port that the Node.js backend listens on.
DB_HOSTBackendfov-dbInternal Docker network hostname to reach the database.
DB_PORTBackend5432Port number for database communication.
DB_USERBackendrootDatabase user profile name.
DB_NAMEBackendfovwebdbExact target database name.
SRT_PORTBackend9999Base port for stream ingest. Scaling this dictates max streams.
FFMPEG_PATHBackend/usr/local/bin/ffmpegAbsolute path inside the container where FFmpeg is installed.
MEDIA_ROOTBackend/var/mediaDirectory mapped for persistent media and HLS storage.
POSTGRES_DBDatabasefovwebdbInstructs PostgreSQL to automatically create a database on startup.
POSTGRES_USERDatabaserootAdministrative user account created for the database instance.
API_URLFrontendhttp://backend:4000Internal route via Docker DNS pointing to the backend API.