LDS Stake Portal

Manual Deployment

Run each Stake Portal service directly on the host without containers. For local development and source-based environments.

This guide is for local development and source-based environments only. For a production server, use Docker Deployment instead. Running services directly from source without containerisation means you are responsible for process supervision, restarts on failure, and keeping runtime versions consistent.

Manual deployment gives you full control over each service and is the right path for:

  • Local development and feature work
  • Contributing to the project
  • Environments where Docker is not available

Prerequisites

RequirementVersionNotes
Node.js18 or laterRequired for the frontend
Python3.11 or laterRequired for backend and Discord bot
uvLatestPython package manager — install here

uv replaces pip, venv, and conda. Never call python or pip directly — always use uv run and uv sync.


Step 1: Clone the Repository

git clone https://github.com/rc27122/LDS_Stake_Website-Template-2.git
cd LDS_Stake_Website-Template-2

Backend

1. Install dependencies

cd backend
uv sync

To include test dependencies:

uv sync --group dev

2. Create the environment file

Copy the example and fill in the required values:

cp .env.example .env

Minimum configuration for local development:

# backend/.env (local dev)
DEV=true
PORT=8000
DB_ENGINE=sqlite
DATABASE_PATH=./database.db
INITIAL_ADMIN_PASSWORD=dev-password-123
JWT_SECRET_KEY=        # generate with: openssl rand -hex 32
SSL_ENABLED=false
SP_APPROVAL_THRESHOLD=2
HC_APPROVAL_THRESHOLD=3
ALLOWED_ORIGINS=http://localhost:3100
FRONTEND_BASE_URL=http://localhost:3100
BACKEND_BASE_URL=http://localhost:8000

JWT_SECRET_KEY must be at least 64 characters. Generate a valid key with openssl rand -hex 32. Do not use openssl rand -base64 32 — that produces only 44 characters and will fail the startup check.

SSL_ENABLED=false is correct for local development because you are not serving over HTTPS. In any production or publicly accessible deployment, set SSL_ENABLED=true to mark session cookies as Secure.

3. Configure backend/wards.csv

Create backend/wards.csv with your wards before the first start. The format is:

name,start_hour
Riverside Ward,9.0
Eastside Ward,11.0
Westview Ward,13.5

start_hour is a decimal hour (9.0 = 9:00 AM, 13.5 = 1:30 PM).

4. Apply database migrations

Migrations run automatically at startup by default (RUN_MIGRATIONS=true). You can also run them manually:

uv run alembic -c alembic.ini upgrade head

5. Start the backend

First run only (creates the admin account):

INITIAL_ADMIN_PASSWORD=dev-password-123 uv run python main.py

All subsequent runs:

uv run python main.py

Or use Uvicorn directly for hot reload during development:

uv run python -m uvicorn src.app:app --reload --host localhost --port 8000

The FastAPI server starts on port 8000. When DEV=true, the Swagger UI is available at http://localhost:8000/docs.

INITIAL_ADMIN_PASSWORD is only read when the database contains no users. After the admin account is created, this variable is ignored on all subsequent starts — the account is not recreated or overwritten.


Frontend

1. Install dependencies

cd frontend
npm install

2. Create the environment file

cp .env.example .env

For local development with the backend on localhost:

# frontend/.env (local dev)
BACKEND_URL=http://localhost:8000
PORT=3100
NODE_ENV=production

BACKEND_URL is read once when the Express server starts. If you change this value, you must restart the server process for the change to take effect.

3. Development modes

Full development stack (Express + Vite, proxies API to backend):

npm run dev

Vite only (fast HMR, no Express proxy — the Vite dev server proxies to http://localhost:8000 directly):

npm run dev:client

Production build + start:

npm run build
node dist/index.cjs

The portal is served at http://localhost:3100. Every /api/* request is proxied to BACKEND_URL with the /api prefix stripped.


Discord Bot (Optional)

The Discord bot is entirely optional. The backend degrades cleanly without it — all Discord-related features are silently disabled when DISCORD_BOT_TOKEN and DISCORD_BOT_URL are not set in backend/.env.

1. Install dependencies

cd discordbot
uv sync

2. Create the environment file

cp .env.example .env
# discordbot/.env (local dev)
DISCORD_TOKEN=your-discord-bot-token
BACKEND_URL=http://localhost:8000
BACKEND_TOKEN=same-shared-secret-as-backend-DISCORD_BOT_TOKEN

Also set the matching variables in backend/.env:

DISCORD_BOT_TOKEN=same-shared-secret-as-discordbot-BACKEND_TOKEN
DISCORD_BOT_URL=http://localhost:8001

BACKEND_TOKEN and DISCORD_BOT_TOKEN must be the same value. See Environment Variables for a full explanation of how this shared secret is used.

3. Start the Discord bot

uv run python main.py

The bot connects to Discord via the gateway API and also starts a FastAPI HTTP server on port 8001 for receiving requests from the main backend.


Startup Order

Always start services in this order:

  1. Backend (uv run python main.py in backend/) — the database and API must be ready first
  2. Frontend (npm run dev in frontend/) — the Express proxy requires the backend to be reachable
  3. Discord bot (uv run python main.py in discordbot/) — optional, requires the backend to be running

Running Tests

# All tests
cd backend
uv run pytest

# Single test file
uv run pytest src/tests/test_auth.py

# Single test
uv run pytest src/tests/test_auth.py::test_login_sets_cookie_and_returns_token

Tests use an isolated temporary SQLite database and never affect the development database.


TypeScript Type Checking

cd frontend
npm run check

Process Management for Long-Running Deployments

If you are running services manually on a server (not recommended for production — use Docker instead), use a process supervisor to ensure services restart after a crash or reboot.

Example startup order for a systemd-based host:

  1. stake-backend.service — starts FastAPI via uv run python main.py
  2. stake-frontend.service — starts Express via node dist/index.cjs (depends on backend)
  3. stake-discordbot.service — optional, starts the Discord bot via uv run python main.py

On this page