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
- One‑line install —
curl -sSL https://ffmpeglab.com/sh/install.sh | bash - Modular scripts — each phase is a separate script for flexibility
- Supabase provides PostgreSQL, pgmq, and S3 storage
- No separate queue database — pgmq runs in the same PostgreSQL instance
- API keys are inserted directly via SQL using the correct schema
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.
(One command runs all phases)
Quick Start – One‑Line Install
Run the main installer to set up everything automatically:
The installer will:
- Check all prerequisites (git, docker, npm, psql, openssl)
- Ask whether you're using Supabase Cloud or self‑hosted Supabase
- Prompt for your Supabase credentials
- Enable pgmq and create the
rendering_queue - Create the storage bucket and set RLS policies
- Clone the FFmpegLab Server repository
- Install dependencies and run TypeORM migrations
- Generate an API key and insert it into the database
- 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
#!/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
#!/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
#!/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
#!/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:
| Service | Provider | Description |
|---|---|---|
| PostgreSQL | Supabase | Primary database with Row Level Security (RLS) |
| pgmq | Supabase | Job queue for asynchronous render processing |
| S3‑compatible Storage | Supabase | File storage for media assets and rendered output |
| REST API | Supabase | Auto‑generated REST API with JWT authentication |
Using Supabase means:
- No separate PostgreSQL setup — it's ready to use
- No separate pgmq installation — enable it with one click
- No separate S3 setup — storage is included
- One DATABASE_URL — connects to everything
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
- A Supabase account (free tier available)
- Docker and Docker Compose installed
- Git, Node.js 18+, psql, openssl, curl
Step 1: Create a Supabase Project
- Go to supabase.com and sign up
- Click New Project
- Fill in: Name (
ffmpeglab), Database Password (use a strong password), Region (choose closest to your users), Pricing Plan (Free) - Click Create new project and wait (1-2 minutes)
Step 2: Enable pgmq
- In the Supabase dashboard, go to Integrations → Supabase Queues
- Click Enable to enable the pgmq extension
- Click Create queue and name it
rendering_queue - Select Basic Queue for durability
CREATE EXTENSION pgmq;
SELECT pgmq.create('rendering_queue');
Step 3: Create a Storage Bucket
- In the Supabase dashboard, click Storage
- Click New Bucket
- Name it
ffmpeglab-assets - Select Private for the bucket type
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
- In the Supabase dashboard, go to Settings → API
- Copy: Project URL, Anon / Public Key, Service Role Key
Step 5: Clone and Configure the Server
cd server
cp .env.example .env
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 run migration:run
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
Troubleshooting
| Issue | Likely Cause | Fix |
|---|---|---|
| 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:
- PostgreSQL — for application data
- pgmq — for durable job queuing
- S3 Storage — for media assets
- Authentication — for API key management
The scripts automate everything:
With your instance up and running, you can begin submitting render jobs, managing projects, and scaling your video processing pipeline — all backed by Supabase.