Reference architecture for provisioning independent client VPS instances under the SIAAS model. Each client VPS is fully isolated, with zero public exposure — all traffic flows through a secure WireGuard tunnel from the primary infrastructure VPS.
┌──────────────────────────────┐ WireGuard Tunnel ┌──────────────────────────────┐
│ Primary Infrastructure │◄─────────────────────────────►│ Client VPS (Isolated) │
│ VPS (veripath.co.uk) │ 10.0.0.1 ◄────────► 10.0.0.x│ │
│ │ │ ┌────────────────────────┐ │
│ ┌────────────────────────┐ │ │ │ PostgreSQL / MySQL │ │
│ │ GP Booking App │ │ │ │ (bind: wg0 only) │ │
│ │ Patient Portal │ │ │ └────────────────────────┘ │
│ │ OpenCode Agent │ │ │ │
│ └────────────────────────┘ │ │ ┌────────────────────────┐ │
│ │ │ │ UFW: DENY all public │ │
│ ┌────────────────────────┐ │ │ │ ALLOW: SSH from CIO │ │
│ │ Security Dashboard │ │ │ │ ALLOW: DB on wg0 only │ │
│ └────────────────────────┘ │ │ └────────────────────────┘ │
└──────────────────────────────┘ └──────────────────────────────┘
Key principles:
wg0)fh_app_user) over the tunnel# From the primary infrastructure VPS
ssh -o StrictHostKeyChecking=no root@<CLIENT_VPS_IP>
The setup is automated via OpenCode. Provide the agent with:
The agent executes these blocks sequentially:
| Block | Action | Details |
|---|---|---|
| 1 | System update & base packages | apt update && apt upgrade -y |
| 2 | Firewall lockdown | ufw default deny incoming, allow SSH only |
| 3 | WireGuard install & configure | Generate keys, write wg0.conf |
| 4 | Database install | PostgreSQL 16, bind to wg0 IP |
| 5 | App user creation | fh_app_user — restricted, tunnel-only |
| 6 | Client admin creation | client_db_admin — full DB privileges |
| 7 | Handover package generation | Credentials, keys, instructions |
[Interface]
Address = 10.0.0.1/24
PrivateKey = <primary-private-key>
ListenPort = 51820
[Peer]
# Client VPS
PublicKey = <client-public-key>
AllowedIPs = 10.0.0.x/32
[Interface]
Address = 10.0.0.x/24
PrivateKey = <client-private-key>
[Peer]
# Primary VPS
PublicKey = <primary-public-key>
Endpoint = <primary-public-ip>:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
Important: The client VPS initiates the outbound connection — this means no public ports need to be open on the client side for the tunnel.
# Install
apt install postgresql-16
# Bind to WireGuard interface only
# Edit /etc/postgresql/16/main/postgresql.conf
listen_addresses = '10.0.0.x' # client's tunnel IP
# Restart
systemctl restart postgresql
CREATE USER fh_app_user WITH PASSWORD '<secure-password>';
GRANT CONNECT ON DATABASE <client_db> TO fh_app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO fh_app_user;
-- Restricted: no DDL, no DROP, no CREATE
CREATE USER client_db_admin WITH PASSWORD '<handover-password>';
GRANT ALL PRIVILEGES ON DATABASE <client_db> TO client_db_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO client_db_admin;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO client_db_admin;
# Default deny
ufw default deny incoming
ufw default allow outgoing
# SSH from client office IP only
ufw allow from <client-office-ip> to any port <custom-ssh-port>
# Database — tunnel only (implicit via bind-address, but belt-and-braces)
# No public rule needed — PostgreSQL listens on wg0 only
# Enable
ufw --force enable
# /etc/ssh/sshd_config
Port <custom-port> # non-default port
PermitRootLogin prohibit-password # key-only
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers clientadmin
authorized_keysEach client VPS runs an automated daily backup via /opt/backup-client.py:
| Item | Schedule | Retention | Location |
|---|---|---|---|
| PostgreSQL dump (pg_dump --clean) | Daily 03:00 UTC | 14 days | /var/backups/practice/daily/ |
| Config files (postgresql.conf, pg_hba.conf) | Daily 03:00 UTC | 14 days | Included in config archive |
| SSH tunnel keys | Daily 03:00 UTC | 14 days | Included in config archive |
| Package manifest (dpkg -l) | Daily 03:00 UTC | 14 days | Included in config archive |
| SHA256 manifest | Daily 03:00 UTC | 14 days | JSON checksum file |
Backups are stored locally on the client VPS. Off-site sync can be configured via the config file (/etc/practice-backup.conf).
GPG encryption is available if a recipient key is configured.
See: infrastructure/SIAAS/client_vps/client_vps_backup for full backup documentation.
When a new client VPS is ready, provide the client with:
client_db_admin user/password) for data migrationsudo systemctl stop wg-quick@wg0
This instantly severs our app's access to their database.Client VPS health is monitored via:
infrastructure/SIAAS/client_vps/dashboard-guide/etc/cron.d/backup-healthThe infrastructure dashboard at https://gp.veripath.co.uk/integrations/infrastructure/ provides a live view of all client VPS status including:
# On primary VPS
sudo wg show
# On client VPS
sudo wg show
# On client VPS
sudo /opt/backup-client.py
ALTER USER fh_app_user WITH PASSWORD '<new-password>';
sudo systemctl stop wg-quick@wg0
sudo systemctl disable wg-quick@wg0
infrastructure/SIAAS/client_vps — Detailed Q&A on VPS architecture decisionsinfrastructure/SIAAS/client_vps/client_vps_backup — Backup strategy documentationinfrastructure/SIAAS/client_vps/dashboard-guide — Understanding the infrastructure dashboardinfrastructure/SIAAS/cold_standby — Disaster recovery proceduresinfrastructure/SIAAS/operational-plan — SIAAS operational roadmap