>FFmpegLab
Self-Hosting & Deployment

Agent-First Setup – FFmpegLab Server with Supabase

Complete guide to setting up FFmpegLab Server using Supabase. Includes modular bash scripts for automated setup of PostgreSQL, pgmq, S3 storage, and API keys.

Setting up FFmpegLab Server from scratch requires a database, a job queue, and storage. Supabase provides all three in one unified platform — PostgreSQL with pgmq for queuing, S3‑compatible storage for media assets, and authentication.

This guide provides modular bash scripts that automate the entire setup process, from configuring Supabase to deploying the server. Each script can be run independently or all together with a single command.

Key takeaways

The Gap: From Zero to Running Server

The FFmpegLab Server documentation covers the architecture, but setting it up from scratch requires knowing the exact steps. This guide fills that gap with a complete, automated approach using modular bash scripts.

install.sh supabase-setup.sh server-setup.sh finalize.sh
(One command runs all phases)

Quick Start – One‑Line Install

Run the main installer to set up everything automatically:

curl -sSL https://ffmpeglab.com/sh/install.sh | bash

The installer will:

  1. Check all prerequisites (git, docker, npm, psql, openssl)
  2. Ask whether you're using Supabase Cloud or self‑hosted Supabase
  3. Prompt for your Supabase credentials
  4. Enable pgmq and create the rendering_queue
  5. Create the storage bucket and set RLS policies
  6. Clone the FFmpegLab Server repository
  7. Install dependencies and run TypeORM migrations
  8. Generate an API key and insert it into the database
  9. Deploy the server with Docker Compose

All logs are written to $HOME/ffmpeglab-setup.log for debugging.

Modular Setup Scripts

The scripts are designed to be run individually or as part of the main installer. They are hosted at ffmpeglab.com/sh/.

1. Main Installer

install.sh ffmpeglab.com/sh/install.sh
#!/bin/bash
set -e

# FFmpegLab Server - Agent-First Setup
# Main installer that orchestrates all phases

BASE_URL="https://ffmpeglab.com/sh"
LOG_FILE="$HOME/ffmpeglab-setup.log"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

echo -e "${BLUE}🚀 FFmpegLab Server - Agent-First Setup${NC}"
echo "================================================"
echo "Logs written to: $LOG_FILE"

# Function to run a phase script
run_phase() {
    local script=$1
    echo -e "${BLUE}▶ Running phase: $script${NC}"
    curl -sSL "$BASE_URL/$script" | bash -s 2>&1 | tee -a "$LOG_FILE"
    if [ ${PIPESTATUS[0]} -ne 0 ]; then
        echo -e "${RED}❌ Phase $script failed. Check $LOG_FILE${NC}"
        exit 1
    fi
    echo -e "${GREEN}✅ Phase $script completed${NC}"
}

# Check prerequisites
echo -e "${YELLOW}🔍 Checking prerequisites...${NC}"
command -v curl >/dev/null 2>&1 || { echo -e "${RED}❌ curl is required.${NC}"; exit 1; }
command -v git >/dev/null 2>&1 || { echo -e "${RED}❌ git is required.${NC}"; exit 1; }
command -v docker >/dev/null 2>&1 || { echo -e "${RED}❌ docker is required.${NC}"; exit 1; }
command -v docker compose >/dev/null 2>&1 || command -v docker-compose >/dev/null 2>&1 || { echo -e "${RED}❌ docker compose is required.${NC}"; exit 1; }
command -v npm >/dev/null 2>&1 || { echo -e "${RED}❌ npm is required.${NC}"; exit 1; }
command -v psql >/dev/null 2>&1 || { echo -e "${RED}❌ psql is required.${NC}"; exit 1; }
command -v openssl >/dev/null 2>&1 || { echo -e "${RED}❌ openssl is required.${NC}"; exit 1; }

echo -e "${GREEN}✅ All prerequisites satisfied.${NC}"

# Ask which Supabase deployment type
echo -e "${YELLOW}📌 Choose Supabase deployment:${NC}"
echo "  1) Supabase Cloud (already created)"
echo "  2) Self-hosted Supabase (run locally)"
read -p "Enter choice [1-2]: " SUPABASE_CHOICE
export SUPABASE_CHOICE

# Run phases
run_phase "supabase-setup.sh"
run_phase "server-setup.sh"
run_phase "finalize.sh"

echo -e "${GREEN}🎉 Setup complete!${NC}"
echo "================================================"
echo -e "${GREEN}🔑 Your API Key: ${API_KEY}${NC}"
echo -e "${BLUE}🌐 API Server: http://localhost:3000${NC}"
echo -e "${YELLOW}💡 To view logs: docker compose logs -f${NC}"
echo -e "${YELLOW}💡 To stop: docker compose down${NC}"
        

2. Supabase Setup

supabase-setup.sh ffmpeglab.com/sh/supabase-setup.sh
#!/bin/bash
set -e

# Supabase Setup Phase
# Configures PostgreSQL, pgmq, storage buckets, and RLS policies

if [ -z "$SUPABASE_CHOICE" ]; then
    echo -e "${RED}❌ SUPABASE_CHOICE not set. Run install.sh first.${NC}"
    exit 1
fi

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

echo -e "${BLUE}🔧 Configuring Supabase...${NC}"

# Get credentials
if [ "$SUPABASE_CHOICE" = "2" ]; then
    # Self-hosted
    echo -e "${BLUE}📦 Setting up self-hosted Supabase...${NC}"
    if [ ! -d "supabase" ]; then
        git clone --depth 1 https://github.com/supabase/supabase.git
        cd supabase/docker
        cp .env.example .env
        echo -e "${YELLOW}⚠️  Please edit supabase/docker/.env to set your secrets.${NC}"
        echo -e "${YELLOW}   Then run: docker compose up -d${NC}"
        read -p "Press Enter after Supabase is running..."
        cd ../..
    else
        cd supabase/docker
        docker compose up -d
        cd ../..
    fi

    if [ -f "supabase/docker/.env" ]; then
        source supabase/docker/.env
        SUPABASE_URL="http://localhost:8000"
        SUPABASE_ANON_KEY="$ANON_KEY"
        SUPABASE_SERVICE_ROLE_KEY="$SERVICE_ROLE_KEY"
        DB_PASSWORD="$POSTGRES_PASSWORD"
        export SUPABASE_URL SUPABASE_ANON_KEY SUPABASE_SERVICE_ROLE_KEY DB_PASSWORD
    else
        echo -e "${YELLOW}⚠️  Could not find .env. Enter credentials manually.${NC}"
        read -p "Supabase URL [http://localhost:8000]: " SUPABASE_URL
        SUPABASE_URL=${SUPABASE_URL:-http://localhost:8000}
        read -p "Supabase Anon Key: " SUPABASE_ANON_KEY
        read -p "Supabase Service Role Key: " SUPABASE_SERVICE_ROLE_KEY
        read -p "PostgreSQL Password: " DB_PASSWORD
        export SUPABASE_URL SUPABASE_ANON_KEY SUPABASE_SERVICE_ROLE_KEY DB_PASSWORD
    fi
else
    # Supabase Cloud
    read -p "Supabase Project URL (e.g., https://your-project.supabase.co): " SUPABASE_URL
    read -p "Supabase Database Password: " DB_PASSWORD
    read -p "Supabase Anon Key: " SUPABASE_ANON_KEY
    read -p "Supabase Service Role Key: " SUPABASE_SERVICE_ROLE_KEY
    export SUPABASE_URL SUPABASE_ANON_KEY SUPABASE_SERVICE_ROLE_KEY DB_PASSWORD
fi

# Build DATABASE_URL
DATABASE_URL="postgresql://postgres:${DB_PASSWORD}@${SUPABASE_URL#https://}/postgres"
export DATABASE_URL

# Storage bucket name
read -p "Storage Bucket Name [ffmpeglab-assets]: " S3_BUCKET
S3_BUCKET=${S3_BUCKET:-ffmpeglab-assets}
export S3_BUCKET

echo -e "${BLUE}🗄️  Enabling pgmq extension...${NC}"
PGPASSWORD="$DB_PASSWORD" psql "$DATABASE_URL" <<EOF
CREATE EXTENSION IF NOT EXISTS pgmq;
SELECT pgmq.create('rendering_queue');
EOF
echo -e "${GREEN}✅ pgmq enabled, queue 'rendering_queue' created.${NC}"

echo -e "${BLUE}📦 Creating storage bucket...${NC}"
curl -s -X POST "${SUPABASE_URL}/storage/v1/bucket" \
  -H "Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"${S3_BUCKET}\",\"public\":false}" > /dev/null || {
    echo -e "${YELLOW}⚠️  Bucket may already exist. Continuing...${NC}"
}
echo -e "${GREEN}✅ Bucket '${S3_BUCKET}' ready.${NC}"

echo -e "${BLUE}🔒 Setting RLS policies...${NC}"
PGPASSWORD="$DB_PASSWORD" psql "$DATABASE_URL" <<EOF
CREATE POLICY "Allow authenticated uploads" ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = '${S3_BUCKET}');
CREATE POLICY "Allow authenticated downloads" ON storage.objects FOR SELECT TO authenticated USING (bucket_id = '${S3_BUCKET}');
CREATE POLICY "Allow authenticated updates" ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = '${S3_BUCKET}');
CREATE POLICY "Allow authenticated deletes" ON storage.objects FOR DELETE TO authenticated USING (bucket_id = '${S3_BUCKET}');
EOF
echo -e "${GREEN}✅ RLS policies set.${NC}"
        

3. Server Setup

server-setup.sh ffmpeglab.com/sh/server-setup.sh
#!/bin/bash
set -e

# FFmpegLab Server Setup Phase
# Clones repo, installs, runs migrations, generates API keys

if [ -z "$DATABASE_URL" ] || [ -z "$S3_BUCKET" ]; then
    echo -e "${RED}❌ DATABASE_URL or S3_BUCKET not set. Run supabase-setup.sh first.${NC}"
    exit 1
fi

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

echo -e "${BLUE}📦 Setting up FFmpegLab Server...${NC}"

# Clone repo
REPO_DIR="server"
if [ ! -d "$REPO_DIR" ]; then
    git clone https://github.com/ffmpeglab/server.git "$REPO_DIR"
fi
cd "$REPO_DIR"

# Generate JWT secret
JWT_SECRET=$(openssl rand -base64 32 2>/dev/null || echo "CHANGE_ME_TO_A_LONG_SECRET_STRING")

# Write .env
cat > .env <<EOF
DATABASE_URL=${DATABASE_URL}
DB_MIGRATION_ENABLED=true
SUPABASE_URL=${SUPABASE_URL}
SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
S3_ACCESS_KEY=${SUPABASE_ANON_KEY}
S3_SECRET_KEY=${SUPABASE_SERVICE_ROLE_KEY}
S3_REGION=us-east-1
S3_ENDPOINT=${SUPABASE_URL}/storage/v1/s3
S3_BUCKET=${S3_BUCKET}
JWT_SECRET=${JWT_SECRET}
PORT=3000
EOF

echo -e "${GREEN}✅ .env created.${NC}"

echo -e "${BLUE}📦 Installing dependencies...${NC}"
npm install

echo -e "${BLUE}🗄️  Running migrations...${NC}"
npm run migration:run || {
    echo -e "${RED}❌ Migrations failed. Check DATABASE_URL and pgmq.${NC}"
    echo -e "${YELLOW}💡 Manual: npx typeorm migration:run -d src/data-source.ts${NC}"
    exit 1
}
echo -e "${GREEN}✅ Migrations complete.${NC}"

# Generate API key
read -p "API Key Prefix [ffmpeglab_sk_]: " API_KEY_PREFIX
API_KEY_PREFIX=${API_KEY_PREFIX:-ffmpeglab_sk_}
API_KEY_SECRET=$(openssl rand -hex 32 2>/dev/null || echo "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6")
API_KEY="${API_KEY_PREFIX}${API_KEY_SECRET}"
export API_KEY

echo -e "${BLUE}💾 Inserting user and API key...${NC}"
PGPASSWORD="$DB_PASSWORD" psql "$DATABASE_URL" <<EOF
INSERT INTO "user" (id, email, password_hash, data)
VALUES (
  '550e8400-e29b-41d4-a716-446655440000',
  '[email protected]',
  '',
  '{"roles": ["admin"]}'
) ON CONFLICT DO NOTHING;

INSERT INTO apikey (id, title, apikey, user_id, data)
VALUES (
  '550e8400-e29b-41d4-a716-446655440001',
  'Admin API Key',
  '${API_KEY}',
  '550e8400-e29b-41d4-a716-446655440000',
  '{"permissions": ["render:*", "project:*", "user:read"]}'
) ON CONFLICT DO NOTHING;
EOF
echo -e "${GREEN}✅ User and API key inserted.${NC}"
        

4. Finalize

finalize.sh ffmpeglab.com/sh/finalize.sh
#!/bin/bash
set -e

# Finalize Phase
# Starts Docker Compose and displays summary

if [ -z "$API_KEY" ]; then
    echo -e "${RED}❌ API_KEY not set. Run server-setup.sh first.${NC}"
    exit 1
fi

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

echo -e "${BLUE}🐳 Starting FFmpegLab Server with Docker Compose...${NC}"
docker compose up -d

echo -e "${BLUE}📊 Service status:${NC}"
docker compose ps

echo -e "${GREEN}✅ Setup complete!${NC}"
echo "================================================"
echo -e "${GREEN}🔑 Your API Key: ${API_KEY}${NC}"
echo -e "${BLUE}🌐 API Server: http://localhost:3000${NC}"
echo -e "${BLUE}📝 Test with:${NC}"
echo "  curl http://localhost:3000/"
echo "  curl -H 'Authorization: Bearer ${API_KEY}' http://localhost:3000/renders"
echo "================================================"
echo -e "${YELLOW}💡 To view logs: docker compose logs -f${NC}"
echo -e "${YELLOW}💡 To stop: docker compose down${NC}"
        

Why Supabase as the Unified Backend

Supabase serves as the full-cycle provider for FFmpegLab Server:

ServiceProviderDescription
PostgreSQLSupabasePrimary database with Row Level Security (RLS)
pgmqSupabaseJob queue for asynchronous render processing
S3‑compatible StorageSupabaseFile storage for media assets and rendered output
REST APISupabaseAuto‑generated REST API with JWT authentication

Using Supabase means:

As the Supabase Queues documentation explains: "Supabase Queues is a Postgres-native durable message queue system built on the pgmq database extension. It offers guaranteed delivery, exactly-once message delivery, and message archival".

Manual Setup Steps

If you prefer to set up manually, here are the steps the scripts automate.

Prerequisites

Step 1: Create a Supabase Project

  1. Go to supabase.com and sign up
  2. Click New Project
  3. Fill in: Name (ffmpeglab), Database Password (use a strong password), Region (choose closest to your users), Pricing Plan (Free)
  4. Click Create new project and wait (1-2 minutes)

Step 2: Enable pgmq

  1. In the Supabase dashboard, go to IntegrationsSupabase Queues
  2. Click Enable to enable the pgmq extension
  3. Click Create queue and name it rendering_queue
  4. Select Basic Queue for durability
-- Alternatively, create the queue via SQL
CREATE EXTENSION pgmq;
SELECT pgmq.create('rendering_queue');

Step 3: Create a Storage Bucket

  1. In the Supabase dashboard, click Storage
  2. Click New Bucket
  3. Name it ffmpeglab-assets
  4. Select Private for the bucket type
-- Create bucket via SQL
INSERT INTO storage.buckets (id, name, public)
VALUES ('ffmpeglab-assets', 'ffmpeglab-assets', false);

-- RLS policies
CREATE POLICY "Allow authenticated uploads" ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'ffmpeglab-assets');
CREATE POLICY "Allow authenticated downloads" ON storage.objects FOR SELECT TO authenticated USING (bucket_id = 'ffmpeglab-assets');

Step 4: Get Supabase Credentials

  1. In the Supabase dashboard, go to SettingsAPI
  2. Copy: Project URL, Anon / Public Key, Service Role Key

Step 5: Clone and Configure the Server

git clone https://github.com/ffmpeglab/server.git
cd server
cp .env.example .env
# Database Configuration
DATABASE_URL=postgresql://postgres:[email protected]:5432/postgres
DB_MIGRATION_ENABLED=true

# Supabase Credentials
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# S3 Storage
S3_ACCESS_KEY=your-anon-key
S3_SECRET_KEY=your-service-role-key
S3_REGION=us-east-1
S3_ENDPOINT=https://your-project.supabase.co/storage/v1/s3
S3_BUCKET=ffmpeglab-assets

# JWT Secret
JWT_SECRET=your_jwt_secret_min_32_chars

Step 6: Run Migrations and Insert API Key

npm install
npm run migration:run
-- Create user
INSERT INTO "user" (id, email, password_hash, data)
VALUES (
  '550e8400-e29b-41d4-a716-446655440000',
  '[email protected]',
  '',
  '{"roles": ["admin"]}'
);

-- Create API key (generate with: openssl rand -hex 32 | sed 's/^/ffmpeglab_sk_/')
INSERT INTO apikey (id, title, apikey, user_id, data)
VALUES (
  '550e8400-e29b-41d4-a716-446655440001',
  'Admin API Key',
  'ffmpeglab_sk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  '550e8400-e29b-41d4-a716-446655440000',
  '{"permissions": ["render:*", "project:*", "user:read"]}'
);

Step 7: Deploy

docker compose up -d

Troubleshooting

IssueLikely CauseFix
Migrations fail Database not accessible or pgmq not enabled Check DATABASE_URL; ensure pgmq extension is enabled
API key not working Key format incorrect or user doesn't exist Verify the key format matches ffmpeglab_sk_ prefix
Runners not picking up jobs pgmq queue not created Create the rendering_queue in Supabase Queues
S3 upload fails Storage bucket not created or RLS policies missing Create the bucket and configure RLS policies
JWT_SECRET error Secret too short Set JWT_SECRET to at least 32 characters

Frequently Asked Questions (FAQ)

What does Supabase provide for FFmpegLab Server?

Supabase provides PostgreSQL with pgmq for job queuing, S3-compatible storage for media assets, authentication via Supabase Auth, and a REST API. It serves as the full-cycle backend provider for FFmpegLab Server.

How do I enable pgmq in Supabase?

Navigate to Integrations → Supabase Queues in your project dashboard and enable the pgmq extension. The extension is available in Postgres 15.6.1.143 or later.

Can I use Supabase Cloud instead of self-hosting?

Yes. Supabase Cloud is the fastest way to get started. Create a free project, enable pgmq, create a storage bucket, and configure your environment variables. The free tier includes 1 GB of storage.

Do I need separate databases for the queue and application data?

No. FFmpegLab Server uses a single PostgreSQL database for both application data and pgmq queues. The DATABASE_URL connection string points to your Supabase PostgreSQL instance.

What are the actual table names in PostgreSQL?

The table names match the entity class names: render, apikey, and logpiece. These are derived from the TypeORM @Entity() decorators.

Final Word

Setting up FFmpegLab Server with Supabase as the unified backend is the fastest and most reliable way to get started. With a single Supabase project, you get:

The scripts automate everything:

curl -sSL https://ffmpeglab.com/sh/install.sh | bash

With your instance up and running, you can begin submitting render jobs, managing projects, and scaling your video processing pipeline — all backed by Supabase.