Server Installation
The Masterplan Optimiser server hosts the web calendar that organisers access on their phones. It runs as a set of Docker containers behind a Caddy reverse proxy with automatic HTTPS. This guide walks you through provisioning a VPS, deploying the application, and creating the first admin account.
Who is this for?
This guide is intended for NC Board Members, coordinators, or technical volunteers who want to provide the web calendar for Head-Organisers in their network. You do not need this if you only use the desktop application.
Requirements
| Requirement | Details |
|---|---|
| VPS | Ubuntu 22.04 or 24.04 (1 vCPU, 1 GB RAM minimum). Providers like Hetzner, DigitalOcean, or Linode all work. |
| Domain name | A domain (e.g. mp-opt.net) pointing to the VPS IP address via an A record. |
| SSH access | Root SSH access to the VPS for the initial setup. |
| Git | The repository must be cloned on the server. |
Step 1 - Provision the VPS
- Create a new VPS with Ubuntu 22.04 or 24.04 at your chosen provider.
- Note the public IP address of the VPS.
- In your domain registrar's DNS settings, create an A record pointing your domain to the VPS IP address.
Type: A Name: @ (or your subdomain) Value: <your-vps-ip> TTL: 300 - Wait for DNS propagation (usually a few minutes to an hour).
Step 2 - Run the Server Setup Script
SSH into your VPS as root and run the setup script. This installs Docker, configures the firewall, and creates a deploy user.
ssh root@<your-vps-ip>
# Download and run the setup script
git clone https://github.com/Brian-Funk/MasterplanOptimiserV3---Server.git /opt/masterplan
cd /opt/masterplan
sudo bash deploy/setup-server.shThe setup script performs the following:
- Updates the system and installs essential packages (curl, git, ufw, fail2ban)
- Configures the firewall (UFW) to allow SSH (22), HTTP (80), and HTTPS (443)
- Installs Docker and the Docker Compose plugin
- Creates a
deployuser with Docker permissions - Sets up the application directory at
/opt/masterplan
Step 3 - Generate the Production Configuration
Run the interactive configuration script. It will prompt you for your domain and generate all required environment variables.
# On the VPS (as deploy user or root)
cd /opt/masterplan
bash configure-production.shThe script will ask for:
| Prompt | Example | Notes |
|---|---|---|
| Domain | mp-opt.net | Required. Must match your DNS. |
| Database password | (auto-generated) | Press Enter to auto-generate a secure password. |
| WebAuthn RP name | GC Calendar | Display name shown during passkey registration. Default is fine. |
This generates a .env file containing all configuration:
DATABASE_URL=postgresql://masterplan:<password>@db:5432/masterplan
POSTGRES_PASSWORD=<auto-generated>
SECRET_KEY=<auto-generated>
CORS_ORIGINS=["https://mp-opt.net"]
WEBAUTHN_RP_ID=mp-opt.net
WEBAUTHN_RP_NAME=GC Calendar
WEBAUTHN_ORIGIN=https://mp-opt.net
COOKIE_SECURE=true
DOMAIN=mp-opt.net
SESSION_TTL_HOURS=8
SESSION_TTL_HOURS_ADMIN=1Important: Keep the .env file safe and do not commit it to version control. It contains database credentials and secret keys. For additional security, you can move sensitive values into Docker Secrets (see Step 4b below).
Step 4 - Deploy the Application
Run the deployment script. This builds the frontend, creates the Docker containers, and starts everything.
cd /opt/masterplan
bash deploy/deploy.shStep 4b - Docker Secrets (Optional)
By default, all secrets live in the .env file. For additional security, you can move SECRET_KEY and VAPID_PRIVATE_KEY into Docker Secrets. Docker Secrets mount as files inside the container at /run/secrets/ and are not visible via docker inspect.
# Create the secrets directory
mkdir -p secrets
# Extract secrets from .env into individual files
grep -oP '^SECRET_KEY=\\K.*' .env > secrets/secret_key
grep -oP '^VAPID_PRIVATE_KEY=\\K.*' .env > secrets/vapid_private_key
# Lock down permissions
chmod 700 secrets
chmod 600 secrets/*Once the secret files are in place, you can remove SECRET_KEY and VAPID_PRIVATE_KEY from your .env file. The application resolves secrets through a priority chain: Docker Secrets files first, then environment variables.
Note: POSTGRES_PASSWORD must remain in .env because Docker Compose uses it to build the DATABASE_URL connection string before containers start.
Redeploy to activate Docker Secrets:
bash deploy/deploy.shVerify the secrets are being loaded from the files by checking the startup logs:
docker logs <backend-container> 2>&1 | head -10
# Should show:
# [Secrets] 'SECRET_KEY' resolved from Docker secret file
# [Secrets] 'VAPID_PRIVATE_KEY' resolved from Docker secret fileThe deployment script performs these steps:
- Pulls the latest code from the repository
- Builds the Next.js frontend as a static export (to
web/out/) - Builds and starts the Docker containers:
- PostgreSQL 16 - database
- FastAPI backend - REST API on port 8000 (internal)
- Caddy - reverse proxy with automatic HTTPS via Let's Encrypt (ports 80 and 443)
- Runs a health check to verify the server is running
Step 5 - Register the Root Admin
- Open your browser and navigate to
https://your-domain.com/bootstrap. - The bootstrap page lets you create the first admin account using a passkey (biometric or security key). This page is only available when no admin account exists yet.
- Enter a display name and register your passkey.
- Once registered, you are logged in as the root admin. You can now manage events, users, and settings from the admin panel.

Server Architecture
| Container | Port | Role |
|---|---|---|
| Caddy | 80, 443 (public) | Reverse proxy, automatic HTTPS (Let's Encrypt), serves static frontend, security headers |
| Backend (FastAPI) | 8000 (internal) | REST API, authentication (passkeys), session management, WebAuthn |
| PostgreSQL 16 | 5432 (internal) | Database for users, events, tasks, and sessions |
Caddy handles all HTTPS certificates automatically - no manual certificate management is needed. The frontend is served as static files from /srv/static, and API requests to /api/* are proxied to the backend container.
Reverse proxy, automatic HTTPS (Let's Encrypt), serves static frontend, security headers
Port 8000 (internal)
- REST API
- WebAuthn (Passkeys)
- Session Management
Port 5432 (internal)
- Users, Events, Tasks
- Sessions & Audit Log
Server architecture: Caddy routes HTTPS traffic to the backend and serves the static frontend.
Updating the Server
To deploy a new version, simply re-run the deploy script:
cd /opt/masterplan
bash deploy/deploy.shThis pulls the latest code, rebuilds the frontend, and restarts the containers with zero-downtime for the database (the PostgreSQL volume is preserved).
Optional - Enable Web Push Notifications
If you want organisers to receive push notifications (e.g. for schedule changes or announcements), add VAPID keys to your .env file:
# Generate VAPID keys (run once on any machine with Python)
python -c "from py_vapid import Vapid; v = Vapid(); v.generate_keys(); print('Private:', v.private_pem()); print('Public:', v.public_key)"
# Add to .env
VAPID_PRIVATE_KEY=<base64url-private-key>
VAPID_CLAIMS_EMAIL=mailto:your-email@example.comAfter adding the keys, redeploy with bash deploy/deploy.sh. If you have set up Docker Secrets (Step 4b), you can also place the VAPID private key into secrets/vapid_private_key and remove it from .env.
Troubleshooting
HTTPS certificate not issued
Make sure your DNS A record is correctly pointing to the VPS IP and has propagated. Caddy needs to reach ports 80 and 443 to obtain a certificate from Let's Encrypt. Check the Caddy logs:
docker compose -f infra/docker-compose.yml logs caddyHealth check fails
Check that all containers are running:
docker compose -f infra/docker-compose.yml -f infra/docker-compose.prod.yml psCheck the backend logs for errors:
docker compose -f infra/docker-compose.yml logs backendBootstrap page not accessible
The /bootstrap route is only available when no admin account exists. If you have already registered an admin, log in normally and manage users from the admin panel.
Database connection error
Ensure the POSTGRES_PASSWORD in your .env matches the password in DATABASE_URL. If you changed the password, you may need to recreate the database volume:
docker compose -f infra/docker-compose.yml down -v
bash deploy/deploy.shNext Steps
- MP Backend Integration - pair the desktop app with this server
- PWA Installation - install the web calendar on organisers' phones
- Google Calendar Integration - optionally publish tasks to Google Calendar
- Admin Panel - manage events, users, and announcements