Server Deployment (Self-Hosted)
What This Is
The server deployment runs SourceQuote as a multi-user web application on your own infrastructure — a VPS, a dedicated server, or any machine you control. It uses:
- Flask as the web application server (via Gunicorn on Linux/macOS, Waitress on Windows)
- PostgreSQL for the database
- Firebase Authentication for user login
- Local filesystem for file storage
- Caddy as the reverse proxy and TLS termination layer
Key characteristics:
- Multi-user — each user signs in with a Firebase account
- Self-hosted — you own and manage the server and all data
- No cloud dependencies — everything runs on your machine (transcription can optionally use Modal.com)
- HTTPS via Caddy — Caddy automatically obtains and renews TLS certificates
- Optional payments — Stripe can be wired up but is not required
Prerequisites: Assumes the development environment is set up as described in ENVIRONMENT_SETUP.md, including PostgreSQL and Firebase.
1. Configure the Server Environment
Copy the example config files if you haven't already:
cp config/server/.env.server.example config/server/.env.server
cp config/server/.secrets.server.example config/server/.secrets.server
Edit config/server/.env.server
Fill in the required values:
FLASK_DEBUG=0
DEPLOY_MODE=server
# Firebase
FIREBASE_PROJECT_ID=your-firebase-project-id
FIREBASE_API_KEY=AIzaSy...
FIREBASE_AUTH_DOMAIN=your-project-id.firebaseapp.com
AUTH_PROVIDER=firebase
# User registration
# true = any Firebase user can self-register on first login
# false = users must be manually activated in the database
ALLOW_OPEN_REGISTRATION=true
# Admin
ADMIN_UIDS=uid1,uid2
ADMIN_EMAIL=you@yourdomain.com
# CORS — list your domain(s) here
ALLOWED_ORIGINS=https://yourdomain.com
# Optional email
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_FROM=you@gmail.com
SMTP_FROM_NAME=SourceQuote
Edit config/server/.secrets.server
# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=your_generated_secret_key
DATABASE_URL=postgresql://postgres:yourpassword@localhost:5432/transcriber
# Path to the Firebase service account JSON you downloaded
FIREBASE_SERVICE_ACCOUNT=/path/to/firebase-service-account.json
# HuggingFace token (for local transcription)
HF_TOKEN=hf_your_token_here
# Optional: Modal.com (for cloud transcription)
MODAL_TOKEN_SECRET=
# Optional: SMTP password
SMTP_PASS=your_app_password
# Optional: Stripe
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
# Optional: GitLab bug reporting
GITLAB_TOKEN=
2. Set Up the PostgreSQL Database
If you haven't already created the database and built the schema:
Create the database:
createdb transcriber
Build the schema (Windows):
database\build.bat -h localhost -p 5432 -U postgres
Build the schema (macOS/Linux):
./database/build.sh -h localhost -p 5432 -U postgres
Pass -P yourpassword if your PostgreSQL user has a password:
database\build.bat -h localhost -p 5432 -U postgres -P yourpassword
3. Create Storage Directories
The app stores uploaded files on the filesystem. Create the data directories if they don't exist:
mkdir -p data/projects data data/embeds
These paths correspond to the PROJECTS_DIR, STATS_DIR, and EMBEDS_DIR settings in .env.server. Defaults (relative to project root): PROJECTS_DIR=data/projects, STATS_DIR=data, EMBEDS_DIR=data/embeds.
4. Start the Server
Set the deploy mode and start:
Windows PowerShell:
$env:DEPLOY_MODE = "server"
python serve.py --host 127.0.0.1 --port 5000
macOS/Linux:
export DEPLOY_MODE=server
python serve.py --host 127.0.0.1 --port 5000
The server listens on 127.0.0.1:5000 by default. Caddy (configured in step 5) handles external traffic and TLS.
Options:
--host HOST Bind address (default: 127.0.0.1)
--port PORT Bind port (default: 5000)
--workers N Gunicorn worker count, Linux/macOS only (default: 4)
On Linux/macOS the server uses Gunicorn. On Windows it uses Waitress. Both are included in
requirements-cpu.txt.
5. Set Up Caddy (Reverse Proxy + HTTPS)
Caddy handles HTTPS certificate provisioning and reverse-proxies requests to Flask.
Install Caddy
- Windows: Download from caddyserver.com/docs/install
- macOS:
brew install caddy - Linux: See caddyserver.com/docs/install for your distro
Edit the Caddyfile
Open Caddyfile in the project root and replace the example domains with your own:
yourdomain.com {
reverse_proxy 127.0.0.1:5000
}
For LAN access with a self-signed certificate (browsers will warn unless you install Caddy's root CA):
# Optional — replace with your server's LAN IP
192.168.1.x {
tls internal
reverse_proxy 127.0.0.1:5000
}
Run Caddy
From the project root:
caddy run
Or as a background service:
caddy start
Caddy automatically obtains a Let's Encrypt certificate for your domain on the first request. Your domain must already point to the server's public IP in DNS.
6. Make Your First Admin User
After the first user registers through Firebase, grant them admin privileges.
Find their Firebase UID from either:
- The Firebase Console → Authentication → Users
- The
userstable in PostgreSQL:SELECT uid, email FROM users;
Then either:
Option A — Edit .env.server:
ADMIN_UIDS=uid_here
Restart the server for the change to take effect.
Option B — Use the helper script:
python scripts/add_admin_user.py --email user@example.com --firebase-uid <uid>
7. Running as a System Service (Linux)
To keep the server running after SSH logout, create a systemd service.
Create /etc/systemd/system/sourcequote.service:
[Unit]
Description=SourceQuote
After=network.target postgresql.service
[Service]
User=youruser
WorkingDirectory=/path/to/transcriber
Environment=DEPLOY_MODE=server
ExecStart=/path/to/transcriber/.venv/bin/python serve.py --host 127.0.0.1 --port 5000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable sourcequote
sudo systemctl start sourcequote
8. Optional: Enable Rate Limiting with Redis
By default, rate limiting uses in-process memory, which does not persist across restarts and is not safe for multi-process deployments. For production use with multiple workers, configure Redis:
- Install Redis:
sudo apt install redis-server - Set in
.env.server:RATELIMIT_STORAGE_URI=redis://localhost:6379/0
9. Optional: Stripe Payments
To enable subscription billing:
- Create a Stripe account and obtain API keys (see
ENVIRONMENT_SETUP.md § 9) - Set
STRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY, andSTRIPE_WEBHOOK_SECRETin.secrets.server - Set
STRIPE_BETA_MODE=0in.env.serverwhen ready to accept real charges - Create a webhook endpoint in the Stripe Dashboard pointing to
https://yourdomain.com/api/stripe/webhook
10. Verify the Deployment
- Navigate to
https://yourdomain.com— the landing page should load over HTTPS - Sign in with a Firebase account — the app should open after login
- Check the admin panel at
https://yourdomain.com/admin(admin users only)
Configuration Reference
| File | Purpose |
|---|---|
config/server/.env.server | Non-secret settings (committed OK if no credentials) |
config/server/.secrets.server | Credentials and API keys (never commit) |
Caddyfile | Caddy reverse proxy config |
serve.py | WSGI launcher (Gunicorn/Waitress) |
database/build.bat / build.sh | Schema initialiser |