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:
| Variable | Required | Description |
|---|---|---|
SECRET_KEY | Yes | Long random string for signing session cookies. Generate with python -c "import secrets; print(secrets.token_hex(32))" |
DATABASE_URL | Yes | PostgreSQL connection string, e.g. postgresql://user:password@localhost:5432/transcriber |
FIREBASE_PROJECT_ID | Yes | Firebase project ID (Firebase console → Project Settings → General) |
FIREBASE_API_KEY | Yes | Firebase web API key |
FIREBASE_AUTH_DOMAIN | Yes | Firebase auth domain, usually <project-id>.firebaseapp.com |
FIREBASE_SERVICE_ACCOUNT | Yes | Path to the Firebase service account JSON key file (Firebase console → Project Settings → Service Accounts → Generate new private key) |
HF_TOKEN | For transcription | HuggingFace read token — required to download Whisper and Pyannote models. Accept licence agreements at the model pages first |
PROJECTS_DIR | No | Directory for uploaded project data. Defaults to data/projects |
ALLOWED_ORIGINS | No | Comma-separated CORS origins, e.g. https://your-domain.com. Use * to allow all |
ALLOW_OPEN_REGISTRATION | No | true to let any Firebase-authenticated user self-register. false to require manual DB activation after first login |
MAX_AUDIO_SIZE_MB | No | Maximum audio upload size in MB. Defaults to 32768 |
FLASK_DEBUG | No | Set to 1 for development mode with auto-reload. Use 0 in production |
Never commit
.envto 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.1pyannote/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
- Open Task Scheduler → Create Task
- General: Name it
Waveform Studio. Check Run whether user is logged in or not. - Triggers: New → At startup
- Actions: New → Start a program
- Program:
C:\path\to\transcriber\.venv\Scripts\python.exe - Arguments:
app.py - Start in:
C:\path\to\transcriber
- Program:
- 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
| Symptom | Check |
|---|---|
| 502 Bad Gateway from Caddy | Is app.py running? Is it bound to 127.0.0.1:5000? |
| Certificate error in browser | Did the domain DNS propagate? Is port 80 open for the ACME challenge? |
| Login always fails | Are the FIREBASE_* variables set correctly in .env? Is the service account key file accessible? |
| CORS errors in browser | Does ALLOWED_ORIGINS include your exact origin (scheme + domain + port)? |
| Models fail to load | Is HF_TOKEN set? Have you accepted the licence agreements on HuggingFace? |
| Database connection error | Is DATABASE_URL correct? Has database/build.sh been run? |
| Self-registration not working | Is ALLOW_OPEN_REGISTRATION=true in .env? |