The GP Booking App (gp_booking_app) is a Django 5.2 multi-tenant healthcare platform serving GP practices, dental surgeries, and partner organisations. It is designed to meet NHS DSPT/DTAC Category 3 compliance requirements.
Live URL: https://gp.veripath.co.uk (served from VPS2 via VPS1 nginx)
Codebase: /root/work/gp_booking_app/ (VPS1 source of truth; deployed copy on VPS2)
Restructure: after the 3-VPS split — see 3-VPS Restructuring Plan
¶ ✅ CURRENT STATUS — 2026-08-16 (read this first)
The 3-VPS split has been RESTORED. As of 2026-08-16 the documented 3-VPS
topology is live again:
- VPS1 (88.208.212.211) — gateway + data + AI: nginx, central Postgres
gp_booking_postgres,client_vps_postgres(canonical client DB), Keycloak,
MinIO, LiveKit, Wiki.js, Forgejo, AES, Netdata, patient portal PWA.- VPS2 (212.227.199.114) — booking apps:
gp_booking_app(Daphne :8000,
celery worker/beat, redis, mailhog) anddental_booking_app(Daphne :8010,
celery worker/beat, redis, orthanc). Reachable from VPS1 via
tun-apps-vps2.service(:8000/:8010); DBs viatun-vps1.services.service.- VPS3 (77.68.112.126) — auxiliary: DefectDojo (
tun-aux-vps3:8090) and
ERPNext (erpnext-deploy, :8005) serving the test-client library
client-VPSs (test-client.accounts.gp/test-client.accounts.dental),
whose ERPNext DBs live on each client's VPS (SIAAS model).Background: VPS2's containers vanished 2026-08-14 16:38. Root cause
established 2026-08-16: an SSH session from VPS1 randocker compose down+
docker system prune -a --volumes --forceon VPS2 (bash history + journalctl
confirm), pruning the GP app images and both external docker networks. Not a
crash and not data loss. Apps were recovered onto VPS1 on 08-15 as an emergency
fallback, then the split was restored on 08-16 with freshly built images. A
container-count watchdog on VPS2 (/opt/scripts/container-watchdog.sh, 5 min)
now catches any repeat in minutes.Repeat-outage protection (added 2026-08-16, same session): VPS2 now
self-heals viavps2-stack-guard.service(timer, every 2 min) — it
recreates the external docker networks if missing and re-runs
docker compose up -d --no-buildfor both booking-app stacks if any
required container is down. A shell guard/etc/profile.d/vps2-guard.sh
blocksdocker compose downanddocker system pruneon VPS2 (use
docker compose stop). See AGENTS.md "⚠️ NEVER run these on VPS2".Config note:
bring-up.shand this page now reflect the restored 3-VPS
topology (VPS2 + VPS3 steps included).
The platform is split across three VPS instances after the restructuring. Inter-VPS
traffic uses persistent SSH tunnels over port 22 (autossh/systemd) — the provider
blocks inbound UDP, so WireGuard cannot be used.
┌──────────────────────────────────────────────────────────────────────────────┐
│ Users (Browsers) / Internet │
│ gp.veripath.co.uk │ dental.veripath.co.uk │ test-client.* │ aes.* │ patient.* │
└──────────────────────────┬───────────────────────────────────────┬───────────┘
│ HTTPS │
┌──────────────────────────▼────────────────────────────────────┐ │
│ VPS1 (88.208.212.211) — Gateway + Data + AI │ │
│ nginx (host-level, SSL termination) + ModSecurity │ │
│ gp_booking_postgres (central DB: dsp_clinic, veripath_dental,│ │
│ wikijs, keycloak, etc.) │ │
│ Keycloak, MinIO, LiveKit, Netdata, Wiki.js, Forgejo, │ │
│ Roundcube, AES (PKI/TSA/Signer/Portal), security-dashboard, │ │
│ patient-portal-pwa, client_db_dental_dev (deferred ERPNext) │ │
└─────────┬─────────────────────────────────────────────────────┴───┬┘
│ SSH tunnels (autossh, port 22) │ SSH tunnel
│ 127.0.0.1:8000 / 8010 (apps → VPS2) │ (8090)
│ 172.18.0.1:5433 (central PG) │
│ 172.18.0.1:8004 (AES) / 5436 / 5437 (client DBs) │
┌─────────▼──────────────────────────────────────────────────────┐ │
│ VPS2 (212.227.199.114) — Core Booking Apps │ │
│ gp_booking_app stack: app (Daphne ASGI) │ │
│ celery_worker/beat (--concurrency=2), gp_booking_redis, │ │
│ client_vps_postgres (172.18.0.1:5435), mailhog │ │
│ dental_booking_app stack: dental_app (Daphne), │ │
│ dental_celery_worker/beat (--concurrency=2), │ │
│ dental_redis, dental_orthanc │ │
│ DB: central via tunnel; tenants via 172.18.0.1:<port> │ │
└────────────────────────────────────────────────────────────────┘ │
│
┌────────────────────────────────────────────────────────────────────▼┐
│ VPS3 (77.68.112.126) — Auxiliary │
│ DefectDojo (security scanning) │
│ (ERPNext deferred) │
└───────────────────────────────────────────────────────────────────┘
Connectivity (SSH tunnels):
127.0.0.1:8000/8010), central Postgres (172.18.0.1:5433),172.18.0.1:8004), client tenant DBs (172.18.0.1:5436/5437).127.0.0.1:8090).tenancy point at 172.18.0.1:<port> (each host's Docker bridge/var/www/gp_booking_app_static/ and/var/www/dental_booking_app_static/, synced from VPS2 every 10 min/opt/scripts/sync-static.sh).In-app architecture (unchanged by the move): the app is a multi-tenant Django
platform with RBAC + tenant middleware, a sector database router
(tenancy.routers.SectorDatabaseRouter), and modules for telehealth (LiveKit),
insurance billing, lab documents, and an AI sidebar.
| Component | Technology |
|---|---|
| Framework | Django 5.2 (Python 3.11) |
| Database | PostgreSQL 15 (central on VPS1, reached via SSH tunnel) + client_vps_postgres (on VPS2) + per-tenant DBs |
| Cache/Broker | Redis 7 / Valkey 7.2 (per-stack on VPS2) |
| Task Queue | Celery + Celery Beat (--concurrency=2 on VPS2) |
| Web Server | Nginx 1.24 (host, VPS1) + Daphne (ASGI, container on VPS2) |
| Container Runtime | Docker Compose (all stacks) |
| Auth | django-oidc + Keycloak 26.6.1 |
| API | Django REST Framework |
| Encryption | django-encrypted-model-fields (Fernet) |
| Security Scanning | Lynis, DefectDojo, Netdata |
| Code Repository | Forgejo 14.0 (git.veripath.co.uk) |
| Documentation | Wiki.js (wiki.veripath.co.uk) |
| Telehealth | LiveKit Server + Telehealth Django Module |
| Digital Signing | AES PKI, TSA, Signer, Portal |
| Accounting | ERPNext 15 (Frappe) |
| Monitoring | Netdata (VPS1) |
| Object Storage | MinIO (S3-compatible, VPS1) |
| VPS3 | 77.68.112.126 — auxiliary (DefectDojo); ERPNext deferred |
The application uses a multi-database architecture with a custom SectorDatabaseRouter for data segregation. The central PostgreSQL 15 instance (gp_booking_postgres) lives on VPS1 (host port 5433) and is reached from the apps on VPS2 via an SSH tunnel (172.18.0.1:5433). Per-client tenant databases run on client_vps_postgres (now on VPS2, 172.18.0.1:5435) and per-client VPS DBs reachable at 172.18.0.1:5436/5437 (tunnelled to VPS1).
dsp_clinic (default) — GP SectorThe primary database holding all GP-related data. Contains the majority of application tables.
⚠️ Configuration note (2026-07-01): The
env.productionfile was previously misconfigured to point atdsp_clinic_devinstead ofdsp_clinic. Thedsp_clinic_devdatabase is a development/corrupted copy missing most application tables (e.g.onboarding_practicedid not exist). Always ensureDB_NAME=dsp_clinicin production config. See the infrastructure fix log for details.
veripath_dental — Dental SectorSeparate database for dental practice data, including:
dental_dentalpractice — Practice detailsdental_dentalprovider — Clinician/provider records (GDC-registered)dental_dentalappointment — Dental appointmentsdental_fp17claim — NHS FP17 claim formsdental_dentaltransaction — Payment transactions (Stripe)dental_dentalclinicalnote — Clinical notes with tooth mappingdental_dentalscheduleblock — Provider schedule blocksUsed by the infrastructure app and client_monitor app:
infrastructure_clientcapacitysnapshot — DB, disk, CPU, memory metrics per tenantinfrastructure_clienttunnelhealth — WireGuard tunnel health checksinfrastructure_clientbackuplog — Backup status trackingNote: The GP and Dental databases share the same PostgreSQL instance but are separate logical databases. The
SectorDatabaseRouterroutes queries based on Django model app labels. Theclient_monitorapp contains unmanaged (managed = False) models that mirror theinfrastructureapp's models — these are used for reading from external client VPS databases via the database router. Client VPS databases are hosted on the secondaryclient_vps_postgresinstance.
The shared PostgreSQL instance hosts the following databases:
| Database | Owner | Purpose |
|---|---|---|
dsp_clinic |
postgres | GP booking app (primary — in use) |
dsp_clinic_dev |
postgres | Dev/staging (partial data — NOT production) |
veripath_dental |
postgres | Dental sector data |
veripath_global |
postgres | Global/shared data |
keycloak |
keycloak | Keycloak auth state |
wikijs |
wikijs | Wiki.js content |
aes_portal |
postgres | AES signing portal |
directus |
postgres | Retired CMS |
test_dsp_clinic |
postgres | Testing |
Additional databases on separate PostgreSQL instances:
forgejo — Forgejo git server (on forgejo_db container at 172.18.0.??)defectdojo — DefectDojo AppSec (on defectdojo_postgres container at 172.18.0.15)erpnext — ERPNext accounting (on erpnext_client_vps_pg container at 172.18.0.3)dsp_clinic/, core/)tenancy.middleware.TenantMiddleware)tenancy/)Sector ──1:N──> Tenant
tenancy.utils provides set_current_tenant(), get_current_tenant(), with_tenant() context manager for Celery tasks and non-HTTP code pathssector_client_test-clinic) with fallback chainusers/)CustomUser (AbstractUser)
├── role: PATIENT | CLINICIAN | RECEPTIONIST | PRACTICE_MANAGER | ADMIN
├── Dental roles: DENTAL_ADMIN, DENTAL_CLINICIAN, DENTAL_HYGIENIST, etc.
├── Partner roles: ORG_ADMIN, PARTNER_ADMIN, PARTNER_REGISTERED_MANAGER, etc.
├── nhs_number (encrypted)
├── phone_number, address, postcode (encrypted)
├── date_of_birth (encrypted)
├── sex (encrypted), emergency_contact_name, emergency_contact_phone (encrypted)
├── verification_level: P5 / P9 / UNVERIFIED
├── org_id (partner org scoping)
├── prescriber_type + professional_registration_number
└── legacy_id (legacy system import)
Supports 20+ roles across GP, Dental, and Partner sectors. All PII encrypted at rest. is_staff is auto-set on save based on role (clinical/admin roles get staff access). REST API endpoints (users/api/) provide user CRUD, patient search, and role management. Patient proxy model and bulk import command (import_patients) available.
appointments/)Appointment ──> Patient (CustomUser)
──> Clinician (CustomUser)
──> Clinic (slot_management)
──> AppointmentType
──> ReminderLog (notifications)
──> PaymentTransaction (1:N)
──> Invoice / InvoiceLineItem
──> InsuranceInvoice (via insurance_billing)
ProviderSession ──> Clinician
──> Clinic
──> AppointmentTypes (M2M)
BookingConfig ──> Practice (1:1)
AppointmentMessage ──> Practice
Key features:
reason fieldPaymentConfig model per practice (encrypted keys), PaymentTransaction (CASH, CARD, BANK_TRANSFER, CHEQUE, STRIPE), webhooks for payment confirmationInvoice and InvoiceLineItem models with PDF generation and email delivery. Insurance billing queue for claim submission.clinical_data/)ClinicalNote ──> Appointment
──> Author (Clinician)
Prescription ──> Patient
──> Prescribing Clinician
──> Appointment (optional)
──> PrescriptionAuditLog
PatientHealthProfile ──> Patient (1:1)
PatientProfile ──> Patient (1:1)
──> PartnerOrg (optional)
──> PatientMedication (1:N)
──> PatientCondition (1:N)
──> PatientAllergy (1:N)
──> PatientConsent (1:N)
PatientImage ──> Patient
──> Appointment (optional)
SnomedICD10Map — SNOMED CT → ICD-10 cross-map
DrugSafetyRule — Drug-disease contraindication alerts
GPNotificationLog — GP surgery notification logs
Key features:
SnomedICD10Map for automated insurance/coding cross-referenceDrugSafetyRule for drug-disease contraindication alerts at prescribing time with BLOCK/WARN severityslot_management/)Clinic ──1:N──> SlotTemplate
pcn_booking/)PCNClinic ──> Clinic
dental/)DentalPractice ──1:N──> DentalProvider
──1:N──> DentalAppointment
──1:N──> DentalRecallRule
──1:N──> DentalAppointmentCategory
──1:1──> DentalPaymentConfig
DentalAppointment ──> Patient
──> DentalProvider
──> DentalPractice
──1:1──> FP17Claim
──1:N──> DentalClinicalNote
──1:N──> DentalTransaction
DentalScheduleBlock ──> DentalPractice
──> DentalProvider
──> DentalAppointmentCategory (zone)
Key features:
partner/)Organisation ──1:1──> PartnerOrg
──1:N──> OnboardingAuditLog
PartnerOrg ──1:N──> BreachReport
──1:N──> MedicalDevice
──1:N──> PatientProfile (via clinical_data)
Key features:
create_partner_realm command using partner-realm-blueprint.json (clients, roles, authentication flows)insurance_billing/)PrivateInsuranceInvoice ──> Appointment (1:1)
PatientPaymentMethod ──> Patient
──> Practice
ClaimExportBatch ──> PrivateInsuranceInvoice (M2M)
Key features:
lab_documents/)ClinicLabConfig ──> Practice (1:1)
LabReportMailbox ──> ClinicLabConfig
LabDocument ──> Patient (optional)
LabReport ──> LabDocument
──> Patient
LabResult ──> LabReport
──> Biomarker
Key features:
telehealth/)VideoRoom ──> Appointment (1:1)
VideoSessionLog ──> VideoRoom
──> Participant (User)
Key features:
VideoRoom status workflow (WAITING → ACTIVE → ENDED)frontend/telehealth/ with components for participant tiles, control bar, waiting screen, consultation notes, call summarystatic/telehealth/telehealth.veripath.co.uk:7880 (WebSocket proxy)patient_portal/)Conversation ──> Patient
──> Practice
Message ──> Conversation
──> Sender (User)
PushSubscription ──> Patient
PrescriptionDispenseLedger ──> Prescription (by ID)
Key features:
onboarding/)ICB ──1:N──> Practice
Practice ──1:N──> StaffRoleRecord
──1:N──> Invitation
──1:N──> InformationAssetRegister
──1:N──> RecordOfProcessingActivity (ROPA)
──1:N──> RetentionTimetable
──1:N──> PolicyAcknowledgment
──1:N──> PolicyDocument (via M2M)
StaffRoleRecord ──> User
──> Practice
──1:N──> StaffOnboardingAudit
RiskManagementRegister (system-wide, optional partner org link)
Comprehensive DSPT compliance features:
staff_management/)LeaveType ──> Practice
LeaveRequest ──> StaffRoleRecord
──> LeaveType
LeaveBalance ──> StaffRoleRecord
──> LeaveType
PracticeTeam ──> Practice
TeamMembership ──> PracticeTeam
──> StaffRoleRecord
notifications/)ReminderLog ──> Appointment
dashboards/)FormalRole (SIRO, CLINICIAN, RECEPTIONIST, etc.)
UserRole ──> User
──> FormalRole
Dashboard (PATIENT, RECEPTIONIST, CLINICIAN, PRACTICE_MANAGER, etc.)
DashboardPermission ──> FormalRole
──> Dashboard
Eight dashboards:
| Dashboard | Data Category | Primary Users |
|---|---|---|
| Patient Dashboard | Clinical | Patients |
| Receptionist Dashboard | Administrative | Receptionists |
| Clinician/GP Dashboard | Clinical | Clinicians |
| Practice Manager Dashboard | Administrative | Practice Managers |
| ICB/Regional Admin Dashboard | Commercial | ICB Admins |
| Super-User/System Admin Dashboard | System | System Admins |
| Accounts & Contract Management | Commercial | Billing Admins |
| Compliance Administration | Administrative | Compliance Officers |
Data segregation enforced: clinical roles cannot access commercial dashboards and vice versa.
Additional dashboards in the partner module:
auditing/)AuditLog ──> User (optional)
clinical_safety/)HazardLog (DCB0129)
reporting/)PlatformActivityEvent ──> Practice
──> User (optional)
MonthlyActivitySummary ──> Practice
CQCAnalyticsCache ──> Practice
ComplianceActivity ──> Practice
──> PartnerOrg (optional)
ICBContract model has been removedintegrations/)NHSIntegrationConfig (GP_CONNECT, PDS, EPS, NHS_NOTIFY, AES)
EmailIntegrationConfig (SMTP, NHSmail)
AgentConfig ──> User (optional), capabilities (JSON)
UserImapAccount ──> User (optional)
LynisScanResult ──> User (optional)
UserImapAccount model with OAuth2 token management (Microsoft Graph), mailbox search, compose/sendai_sidebar/)NoteMapping (URL pattern → note path)
SidebarNote (cached rendered HTML)
SidebarSettings (key-value config)
infrastructure/)ClientCapacitySnapshot ──> Tenant
ClientTunnelHealth ──> Tenant
ClientBackupLog ──> Tenant
The infrastructure app provides a per-tenant client VPS dashboard at /infrastructure/. It displays:
client_monitor/)ClientCapacitySnapshot (unmanaged, reads external VPS DB)
ClientTunnelHealth (unmanaged, reads external VPS DB)
ClientBackupLog (unmanaged, reads external VPS DB)
Contains unmanaged (managed = False) models that mirror the infrastructure app's models. These are used for reading from external client VPS databases via the database router connected through WireGuard tunnels. Enables the principal VPS to display infrastructure metrics from client VPS instances.
Beyond the GP Booking App, the principal VPS hosts additional services, all running as Docker containers on the gp_booking_app_gp_booking_network:
| Service | Container | Network IP | Host Port | URL / Purpose |
|---|---|---|---|---|
| Wiki.js | wikijs |
172.18.0.11 | 3000 | wiki.veripath.co.uk |
| Forgejo | forgejo |
172.18.0.7 | 3000 | git.veripath.co.uk |
| Keycloak | keycloak |
172.18.0.18 | 8443→8080 | auth.veripath.co.uk |
| PostgreSQL (main) | gp_booking_postgres |
— | 127.0.0.1:5433→5432 | All databases (VPS1; VPS2 apps reach it via tunnel 172.18.0.1:5433) |
| PostgreSQL (client VPS) | client_vps_postgres |
— | 172.18.0.1:5435→5432 | Client VPS databases (on VPS2) |
| Redis | redis |
172.18.0.17 | 6379 | Celery broker (moved to VPS2) |
| MailHog | mailhog |
172.18.0.5 | 1025/8025 | Dev email capture |
| LiveKit | livekit |
host | 7880-7881, 3478/udp | Telehealth video (telehealth.veripath.co.uk) |
| Roundcube | roundcube |
172.18.0.24 | 8082→80 | mail.veripath.co.uk |
| Patient Portal PWA | patient-portal-pwa |
172.22.0.7 (dev net) | 8081→80 | patient.veripath.co.uk |
| AES PKI | aes-pki |
host | 8001 | Certificate authority |
| AES TSA | aes-tsa |
host | 8002 | Timestamp authority |
| AES Signer | aes-signer |
host | 8003 | Digital signing |
| AES Portal | aes-portal |
172.18.0.14 | 8004 | aes.veripath.co.uk |
| Security Dashboard | security-dashboard |
172.18.0.8 | 8006 | security.veripath.co.uk |
| DefectDojo | multiple | — | 8090 (VPS3) | AppSec vulnerability mgmt (moved to VPS3) |
| MinIO | minio |
172.18.0.6 | 9000 | S3 object storage |
| Netdata | netdata |
host | — | System monitoring |
| ERPNext | erpnext_web, erpnext_worker, erpnext_scheduler |
— | 8005→8000 | accounts.veripath.co.uk (stopped / deferred) |
| pgAdmin | — | host | 5050 | pgadmin.veripath.co.uk database management |
| Celery Beat | gp_booking_celery_beat |
— | — | Periodic task scheduler |
| Integration | Purpose | Endpoint |
|---|---|---|
| GP Connect | Patient record retrieval (FHIR) | clinical_data/integrations/gp_connect.py |
| PDS | Personal Demographics Service | Configured in settings |
| EPS | Electronic Prescription Service | clinical_data/integrations/eps_fhir.py |
| NHS Notify | Appointment reminders | services/nhs_notify.py |
| NHS OAuth2 | Authentication | clinical_data/integrations/nhs_auth.py |
All integrations operate in sandbox mode by default (USE_MOCK_EPS = True).
integrations appAll sensitive patient and staff data is encrypted at rest using django-encrypted-model-fields (Fernet symmetric encryption):
/etc/nginx/sites-enabled/gp_booking_app_gp_booking_network (172.18.0.0/16, bridge) on| Service | Container | Image | Port Mapping |
|---|---|---|---|
| Daphne App (VPS2) | gp_booking_app |
gp_booking_app-app |
127.0.0.1:8000→8000 |
| Celery Worker | gp_booking_celery_worker |
gp_booking_app-celery_worker |
None |
| Celery Beat | gp_booking_celery_beat |
gp_booking_app-celery_beat |
None |
| PostgreSQL (main) | gp_booking_postgres |
pgvector/pgvector:pg15 |
127.0.0.1:5433→5432 |
| PostgreSQL (client VPS) | client_vps_postgres |
postgres:15-alpine |
172.18.0.1:5435→5432 |
| Redis | redis |
redis:7-alpine |
None |
| Keycloak | keycloak |
quay.io/keycloak/keycloak:26.6.1 |
127.0.0.1:8443→8080 |
| MailHog | mailhog |
mailhog/mailhog:latest |
1025, 8025 |
| ERPNext Web | erpnext_web |
erpnext_v15:latest |
127.0.0.1:8005→8000 |
| ERPNext Worker | erpnext_worker |
erpnext_v15:latest |
None |
| ERPNext Scheduler | erpnext_scheduler |
erpnext_v15:latest |
None |
| ERPNext PostgreSQL | erpnext_client_vps_pg |
postgres:15-alpine |
127.0.0.1:5436→5432 |
⚠️ Legacy (pre-restructure): the standalone
docker runcommands below use the
old/var/www/html/gp_booking_apppath and Gunicorn. The app now runs via
docker compose on VPS2 (/root/work/gp_booking_app/docker-compose.yml) with
Daphne (ASGI). Keep these for reference only.
# Start the app (standalone docker, not docker-compose):
docker run -d --name gp_booking_app \
--network gp_booking_app_gp_booking_network \
-p 127.0.0.1:8000:8000 \
-v /var/www/html/gp_booking_app:/app \
-v /var/www/html/gp_booking_app/staticfiles:/app/staticfiles \
-v /var/www/html/gp_booking_app/media:/app/media \
--restart unless-stopped \
--env-file /var/www/html/gp_booking_app/config/secrets/env.production \
gp_booking_app_msal
# Management inside container
docker exec gp_booking_app python manage.py migrate
docker exec gp_booking_app python manage.py createsuperuser
docker exec gp_booking_app python manage.py update_agent_prompts
docker exec gp_booking_app python manage.py create_partner_realm
docker exec gp_booking_app python manage.py seed_insurance_billing
docker exec gp_booking_app python manage.py import_patients
# Deploy veripath.co.uk website
/opt/deploy-veripath-site.sh
# View app logs
docker logs gp_booking_app
# Celery worker (separate container)
docker run -d --name gp_booking_celery_worker \
--network gp_booking_app_gp_booking_network \
-v /var/www/html/gp_booking_app:/app \
--restart unless-stopped \
--env-file /var/www/html/gp_booking_app/config/secrets/env.production \
gp_booking_app_msal \
celery -A dsp_clinic worker --loglevel=info
# Celery beat (separate container)
docker run -d --name gp_booking_celery_beat \
--network gp_booking_app_gp_booking_network \
-v /var/www/html/gp_booking_app:/app \
--restart unless-stopped \
--env-file /var/www/html/gp_booking_app/config/secrets/env.production \
gp_booking_app_msal \
celery -A dsp_clinic beat --loglevel=info
Nginx runs on the Docker host. Key upstream targets:
| Domain | Upstream | Service |
|---|---|---|
gp.veripath.co.uk |
127.0.0.1:8000 |
Django App (GP) |
*.gp.veripath.co.uk |
127.0.0.1:8000 |
Per-tenant subdomains (wildcard) |
dental.veripath.co.uk |
127.0.0.1:8000 |
Django App (Dental) — ✅ fixed 2026-07-22 |
test-client.gp.veripath.co.uk |
127.0.0.1:8000 |
Django App |
patient.veripath.co.uk |
127.0.0.1:8000 (app) + 127.0.0.1:8081 (PWA) |
Patient Portal |
telehealth.veripath.co.uk |
127.0.0.1:7880 |
LiveKit WebSocket proxy |
auth.veripath.co.uk |
127.0.0.1:8443 |
Keycloak |
wiki.veripath.co.uk |
127.0.0.1:3002 |
Wiki.js |
git.veripath.co.uk |
127.0.0.1:3000 |
Forgejo |
mail.veripath.co.uk |
127.0.0.1:8082 |
Roundcube |
security.veripath.co.uk |
127.0.0.1:8006 |
Security Dashboard |
aes.veripath.co.uk |
127.0.0.1:8004 |
AES Portal |
accounts.veripath.co.uk |
127.0.0.1:8005 |
ERPNext Accounting |
pgadmin.veripath.co.uk |
127.0.0.1:5050 |
pgAdmin |
gp.protocoldata.co.uk |
gp.veripath.co.uk (alias) |
Domain alias |
veripath.co.uk |
— | Nuxt 3 site (separate container) |
The per-tenant client VPS dashboard is available at:
/infrastructure/infrastructure/views.py — InfrastructureDashboardViewThis system is built around the Sovereign Infrastructure as a Service (SIAAS) model:
systemctl stop wg-quick@wg0)See infrastructure/SIAAS/client_vps_setup for the full client VPS provisioning blueprint.
| Issue | Status | Date Fixed |
|---|---|---|
| Wiki-manage.py stale IPs | DB host 172.18.0.7→172.18.0.15 (now 172.18.0.4), API URL 172.18.0.5→172.18.0.11 | 2026-07-01 |
| dsp_clinic_dev vs dsp_clinic | env.production was pointing to corrupted dev DB. Changed to dsp_clinic. | 2026-07-01 |
| Nginx upstream IP stale | App container IP changed on restart. Using host port mapping (127.0.0.1:8000). | 2026-07-01 |
| PostgreSQL/Redis/Keycloak down | All three containers exited simultaneously. Root cause TBD. Restarted. | 2026-07-01 |
| dental.veripath.co.uk upstream stale | Nginx at 172.18.0.6:8000 (old container IP). Fixed to 127.0.0.1:8000. | 2026-07-22 ✅ |
| Patient Portal PWA on separate network | PWA container is on gp_booking_app_dev_gp_booking_network (172.22.0.0/16), not main bridge network. Not a bug but worth noting. | — |
| Duplicate app containers | infallible_varahamihira, gracious_nobel removed. All containers now Compose-managed. |
2026-07-22 ✅ |
============================================================
| Change | Details |
|---|---|
| Compose migration | All stacks migrated to Docker Compose with shared external network |
| Healthchecks | All services now have healthchecks |
| Resource limits | CPU and memory limits set on all services |
| Log rotation | JSON-file driver with 10MB max-size, 3-file max |
| Secrets | Passwords moved to .env files, never hardcoded |
| Celery broker | Redis configured as Celery broker with password auth |
| Health endpoint | /infrastructure/health/ added to Django app |
| Ghost containers | infallible_varahamihira, gracious_nobel removed |
| Dead containers | 4 exited containers removed |
| Orphaned volumes | 16 hash-named volumes removed (~130MB recovered) |
| Nginx — all internal IPs eliminated | wiki (172.18.0.11), git (172.18.0.7), security/dojo (172.18.0.24), jorja-essex, maple-surgery (172.18.0.6) — all replaced with 127.0.0.1 |
| Wiki.js port | Moved to 127.0.0.1:3002 (previously no host port) |
| DefectDojo | Added 127.0.0.1:8080 host port mapping |
| LiveKit config | Fixed — added keys section |
| patient-portal-pwa | Migrated to Compose, connected to shared network |
| security-dashboard | Migrated to Compose |
| Conventions documented | https://wiki.veripath.co.uk/en/development/projects/compose-conventions |
Status: DONE 2026-08-16. The 3-VPS split was restored on 2026-08-16
(apps back on VPS2, ERPNext on VPS3, tunnels + bring-up.sh + this page updated).
Root cause of the 08-14 disappearance was established (deliberate
docker compose down+ prune from VPS1-origin SSH — see status block above).
The checklist below is retained as the historical record of what was executed.
Goal: booking apps (gp + dental) back on VPS2 (212.227.199.114); VPS1 keeps
gateway + data + AI; VPS3 keeps DefectDojo + DR. Restore documented resilience
(dedicated cores, no single point of failure).
Checklist (in order):
/var/lib/docker/containers/ last modified 2026-08-14 16:38; containers gone,gp_booking_app_client_postgres_data 71MB,dental_booking_app_orthanc-data 668K). Do NOT trust VPS2 until we know why./root/work/gp_booking_app + /root/work/dental_booking_app,.env/env.production presence.docker compose up -d --no-build for both repos)dsp_clinic_dev back on VPS2 client_vps_postgrestest-client id 3, port 172.18.0.1:5435), orclient_vps_postgres on VPS1).client_vps_postgres compose + tenant db_host/db_port to match.tun-apps-vps2.service (VPS1 nginx → VPS2 apps 8000/8010)172.18.0.1:5433) + tenant DBs172.18.0.1:5436/5437); re-point nginx gp.veripath.co.uk → VPS2 :8000 anddental.veripath.co.uk → VPS2 :8010 (remove the 8011 demo override).bring-up.sh (add VPS2 step), /root/AGENTS.md,/opt/check-tunnel-health.py), backups green.Notes / constraints:
bring-up.sh + infra-bringup.service currently assume single-primary — update both.docker compose down VPS1's stacks until VPS2 is verified serving (keep a============================================================
Page: https://wiki.veripath.co.uk/development/projects/gp_booking_app
============================================================
Page: https://wiki.veripath.co.uk/development/projects/gp_booking_app
============================================================
Page: https://wiki.veripath.co.uk/development/projects/gp_booking_app
============================================================
Page: https://wiki.veripath.co.uk/development/projects/gp_booking_app