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
| Requirement | Version | Notes |
|---|---|---|
| Node.js | 18 or later | Required for the frontend |
| Python | 3.11 or later | Required for backend and Discord bot |
| uv | Latest | Python 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-2Backend
1. Install dependencies
cd backend
uv syncTo include test dependencies:
uv sync --group dev2. Create the environment file
Copy the example and fill in the required values:
cp .env.example .envMinimum 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:8000JWT_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.5start_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 head5. Start the backend
First run only (creates the admin account):
INITIAL_ADMIN_PASSWORD=dev-password-123 uv run python main.pyAll subsequent runs:
uv run python main.pyOr use Uvicorn directly for hot reload during development:
uv run python -m uvicorn src.app:app --reload --host localhost --port 8000The 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 install2. Create the environment file
cp .env.example .envFor local development with the backend on localhost:
# frontend/.env (local dev)
BACKEND_URL=http://localhost:8000
PORT=3100
NODE_ENV=productionBACKEND_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 devVite only (fast HMR, no Express proxy — the Vite dev server proxies to http://localhost:8000 directly):
npm run dev:clientProduction build + start:
npm run build
node dist/index.cjsThe 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 sync2. 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_TOKENAlso set the matching variables in backend/.env:
DISCORD_BOT_TOKEN=same-shared-secret-as-discordbot-BACKEND_TOKEN
DISCORD_BOT_URL=http://localhost:8001BACKEND_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.pyThe 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:
- Backend (
uv run python main.pyinbackend/) — the database and API must be ready first - Frontend (
npm run devinfrontend/) — the Express proxy requires the backend to be reachable - Discord bot (
uv run python main.pyindiscordbot/) — 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_tokenTests use an isolated temporary SQLite database and never affect the development database.
TypeScript Type Checking
cd frontend
npm run checkProcess 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:
stake-backend.service— starts FastAPI viauv run python main.pystake-frontend.service— starts Express vianode dist/index.cjs(depends on backend)stake-discordbot.service— optional, starts the Discord bot viauv run python main.py