SERVER_DEPLOYMENT

Server Deployment Guide

This guide covers deploying Waveform Studio from a fresh clone to a running production server, on both Linux and Windows.


Prerequisites

Both platforms

  • Python 3.10+
  • Node.js 18+ (for JS tooling — linting, docs, tests, and building the standalone app)
  • PostgreSQL (database backend)
  • Git (to clone the repository)
  • Caddy (reverse proxy — handles HTTPS and TLS certificates automatically)
  • A Firebase project (for user authentication)
  • A domain name pointed at your server's public IP

Checking versions

python --version      # or python3 --version on Linux
node --version
psql --version

1. Get the code

git clone <repository-url>
cd transcriber

2. Create a virtual environment

Linux:

python3 -m venv .venv
source .venv/bin/activate

Windows:

python -m venv .venv
.venv\Scripts\activate

3. Install Python dependencies

Choose the requirements file that matches your hardware. GPU Torch is large, so use the CPU build if you do not plan to run server-side transcription.

CPU-only server:

pip install -r requirements-cpu.txt

GPU server (NVIDIA, CUDA 12.4):

pip install -r requirements-gpu.txt

The GPU requirements install CUDA-specific builds of PyTorch, torchaudio, pyannote.audio, and faster-whisper. The CPU builds use standard PyPI wheels.


4. Install JS dependencies

npm install

This installs ESLint, Vitest, JSDoc tooling, and Husky pre-commit hooks. It is also required to build the standalone desktop executable.


5. Set up the database

Create a PostgreSQL database named transcriber, then run the schema build scripts to create all tables:

Linux / macOS:

./database/build.sh

Windows:

database\build.bat

Custom connection (host, port, user, password):

./database/build.sh -h localhost -p 5432 -U postgres -P yourpassword

The scripts apply SQL files from database/postgres/ in dependency order and will drop and rebuild all tables. Run this once on a fresh database, or again to fully reset it.


6. Configure the environment

Copy the example file and fill in the values:

Linux:

cp .env.example .env

Windows:

copy .env.example .env

Open .env and set the following:

VariableRequiredDescription
SECRET_KEYYesLong random string for signing session cookies. Generate with python -c "import secrets; print(secrets.token_hex(32))"
DATABASE_URLYesPostgreSQL connection string, e.g. postgresql://user:password@localhost:5432/transcriber
FIREBASE_PROJECT_IDYesFirebase project ID (Firebase console → Project Settings → General)
FIREBASE_API_KEYYesFirebase web API key
FIREBASE_AUTH_DOMAINYesFirebase auth domain, usually <project-id>.firebaseapp.com
FIREBASE_SERVICE_ACCOUNTYesPath to the Firebase service account JSON key file (Firebase console → Project Settings → Service Accounts → Generate new private key)
HF_TOKENFor transcriptionHuggingFace read token — required to download Whisper and Pyannote models. Accept licence agreements at the model pages first
PROJECTS_DIRNoDirectory for uploaded project data. Defaults to data/projects
ALLOWED_ORIGINSNoComma-separated CORS origins, e.g. https://your-domain.com. Use * to allow all
ALLOW_OPEN_REGISTRATIONNotrue to let any Firebase-authenticated user self-register. false to require manual DB activation after first login
MAX_AUDIO_SIZE_MBNoMaximum audio upload size in MB. Defaults to 32768
FLASK_DEBUGNoSet to 1 for development mode with auto-reload. Use 0 in production

Never commit .env to version control. It is already listed in .gitignore.

HuggingFace model licences

If you use server-side transcription, you must accept the licence agreements for both models on the HuggingFace website before the server will be able to download them:

  • pyannote/speaker-diarization-3.1
  • pyannote/segmentation-3.0

7. Install Caddy

Caddy is a reverse proxy that sits in front of the app and handles HTTPS automatically, obtaining and renewing TLS certificates from Let's Encrypt with no manual configuration.

Linux (Debian/Ubuntu):

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Windows:

winget install CaddyServer.Caddy

Verify the installation:

caddy version

8. Configure Caddy

Edit Caddyfile and replace your-domain.com with your actual domain:

your-domain.com {
    reverse_proxy 127.0.0.1:5000
}

Caddy will automatically obtain and renew a Let's Encrypt certificate as long as:

  • Your domain's DNS A record points to this server's public IP
  • Ports 80 and 443 are open on your firewall (see step 9)

Optional: LAN access by IP

Uncomment the second block in Caddyfile to also allow direct access by IP address over HTTPS using a self-signed certificate:

:443 {
    tls internal
    reverse_proxy 127.0.0.1:5000
}

Run caddy trust once on the server to install the local CA. To avoid browser warnings on other devices, install the CA cert on each device manually (caddy root prints its path).


9. Open firewall ports

Caddy needs ports 80 (for ACME HTTP challenges) and 443 (HTTPS).

Linux (ufw):

sudo ufw allow 80
sudo ufw allow 443

Windows: Open Windows Defender Firewall → Inbound Rules → New Rule → Port → TCP 80, 443.


10. Start the server

Linux:

./run.sh

Windows:

run.bat

In a separate terminal, start Caddy:

caddy run --config Caddyfile

Visit https://your-domain.com to confirm everything is working.


11. Run as a background service

For the server to survive reboots, install both the app and Caddy as services.


Linux — systemd

Create /etc/systemd/system/transcriber.service:

[Unit]
Description=Waveform Studio
After=network.target

[Service]
User=<your-user>
WorkingDirectory=/path/to/transcriber
ExecStart=/path/to/transcriber/.venv/bin/python app.py
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now transcriber

Caddy's apt package registers its own systemd service automatically:

sudo systemctl enable --now caddy

Copy your Caddyfile to /etc/caddy/Caddyfile, or point the service at the project's copy by editing /lib/systemd/system/caddy.service.

Check service logs:

sudo journalctl -u transcriber -f
sudo journalctl -u caddy -f

Windows — Task Scheduler

  1. Open Task Scheduler → Create Task
  2. General: Name it Waveform Studio. Check Run whether user is logged in or not.
  3. Triggers: New → At startup
  4. Actions: New → Start a program
    • Program: C:\path\to\transcriber\.venv\Scripts\python.exe
    • Arguments: app.py
    • Start in: C:\path\to\transcriber
  5. Settings: Uncheck Stop task if it runs longer than

Repeat for Caddy:

  • Program: C:\path\to\caddy.exe
  • Arguments: run --config C:\path\to\transcriber\Caddyfile

Alternatively, use NSSM (Non-Sucking Service Manager) to register both as proper Windows services:

nssm install WaveformStudio "C:\path\to\.venv\Scripts\python.exe" "app.py"
nssm set WaveformStudio AppDirectory "C:\path\to\transcriber"
nssm start WaveformStudio

Building the standalone desktop app

To produce a self-contained Windows executable (no server or cloud dependencies required), run:

npm run build:local

This invokes PyInstaller with local.spec and writes the output to build/local/WaveformStudio.exe. The executable bundles all templates, static assets, application code, and a SQLite schema. See LOCAL_MODE in README.md for a full explanation of how the standalone build differs from the server deployment.


Troubleshooting

SymptomCheck
502 Bad Gateway from CaddyIs app.py running? Is it bound to 127.0.0.1:5000?
Certificate error in browserDid the domain DNS propagate? Is port 80 open for the ACME challenge?
Login always failsAre the FIREBASE_* variables set correctly in .env? Is the service account key file accessible?
CORS errors in browserDoes ALLOWED_ORIGINS include your exact origin (scheme + domain + port)?
Models fail to loadIs HF_TOKEN set? Have you accepted the licence agreements on HuggingFace?
Database connection errorIs DATABASE_URL correct? Has database/build.sh been run?
Self-registration not workingIs ALLOW_OPEN_REGISTRATION=true in .env?