This page documents dental-specific differences from the standard client VPS onboarding process. For the full step-by-step runbook, see the On-boarding New Clients page.
All infrastructure (Docker image, tunnel, Cloud-Init) is identical for GP and dental clients. The differences are at the configuration layer.
Status (2026-08-02): The dental app is deployed as its own stack (
dental_appcontainer on port 8010, separate repomatthew/dental_booking_app). Client VPS onboarding fortest-client-dentalis complete and live athttps://test-client-dental.dental.veripath.co.uk.
| Aspect | GP Client | Dental Client |
|---|---|---|
| URL pattern | {slug}.gp.veripath.co.uk |
{slug}.dental.veripath.co.uk |
| Nginx config | {slug}.gp.veripath.co.uk.conf |
{slug}.dental.veripath.co.uk.conf |
| SSL cert domain | {slug}.gp.veripath.co.uk |
{slug}.dental.veripath.co.uk |
| Parent tenant sector | GP | Dental |
| DB naming | {slug}_clinic |
{slug}_dental |
| User roles | CLINICIAN, RECEPTIONIST, etc. |
DENTAL_CLINICIAN, DENTAL_ADMIN, DENTAL_HYGIENIST |
| PartnerOrg sector | GP | Dental |
| Dashboard | GP dashboards | Dental dashboards + partner dashboards |
| App container port | 127.0.0.1:8000 (gp_booking_app) |
127.0.0.1:8010 (dental_app) |
| Tunnel port | 5435 | 5436 |
Dental client DBs use the SSH reverse tunnel as the standard path (WireGuard UDP is provider-filtered between VPSes). The dental tunnel rides port 5436 so it never collides with the GP test-client tunnel on 5435.
Connection chain:
dental_app container -> 172.18.0.1:5436 -> socat (dental-tunnel-forward) -> 127.0.0.1:5436 -> SSH reverse tunnel -> client VPS:5432
Setup on the primary VPS:
/etc/systemd/system/dental-tunnel-forward.service — socat 172.18.0.1:5436 → 127.0.0.1:5436permitlisten="127.0.0.1:5436" and permitlisten="172.18.0.1:5436"client-postgres:ssh-v1.1.0 with TUNNEL_PORT=5436Use the migrate_client management command — the dynamic per-client DB alias (sector_client_{slug}) isn't registered at argparse time, so the plain migrate --database=... CLI command fails.
docker exec dental_app python manage.py migrate_client --slug test-client-dental
docker exec dental_app python manage.py migrate_client --slug test-client-dental --plan # dry-run
Client data migrations (clinical_data.0010, users.0013, tenancy.0002, dashboards.0004) are DB-aware — they detect a client-family target DB (sector_client / sector_client_*) and no-op automatically, so no --fake step is required on client DBs.
ERPNext is the GP accounting subsystem (Frappe, per-practice site {practice}.accounts.gp.veripath.co.uk). Dental billing is Stripe-based and the dental app currently has no ERPNext integration.
Do NOT create an ERPNext
site_config.jsonfor dental clients. The runbook's Phase 4 ERPNext step applies to GP only.A dental→ERPNext integration has been parked (2026-08-02). Note for a future session: the GP "accounting-only" ERPNext is the full ERPNext app with non-accounting modules hidden in the UI — there is no schema-level "Accounts only" install, so a dental ERPNext site would carry the full ~700-table footprint per practice. See Dental App Developer Plan for the parked item and open questions.
A reusable template is available at /etc/nginx/sites-available/dental-client-template.conf:
server {
server_name {slug}.dental.veripath.co.uk;
location / {
proxy_pass http://127.0.0.1:8010; # dental app container (NOT 8000)
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/{slug}.dental.veripath.co.uk/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{slug}.dental.veripath.co.uk/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = {slug}.dental.veripath.co.uk) {
return 301 https://$host$request_uri;
}
listen 80;
server_name {slug}.dental.veripath.co.uk;
return 404;
}
Note: the template proxies to
127.0.0.1:8010(the dental app container), not 8000. A test configtest-client-dental.dental.veripath.co.uk.confexists insites-available— symlink it intosites-enabledonly once a real dental client VPS tenant is created.
Dental client subdomains are covered by the *.dental.veripath.co.uk wildcard → 88.208.212.211. No per-client A record is required.
* matches only a fixed prefix; https://*.dental.veripath.co.uk/oidc/* treats the host *.dental literally, so it does NOT match test-client-dental.dental.veripath.co.uk. Each client host needs an explicit redirect URI in the dental-booking-app client, e.g. https://test-client-dental.dental.veripath.co.uk/oidc/* (and a root https://test-client-dental.dental.veripath.co.uk/* for the post-logout redirect).role/org_id claims. The dental client had no mappers, so user attributes never reached the app (logins created role=ADMIN). Add oidc-usermodel-attribute-mapper mappers for role and org_id (same config as the GP client).role=<DENTAL_ROLE> and org_id=<slug> (mapped into ID/access tokens by the mappers above).The following changes are present in the dental app (matthew/dental_booking_app, branch develop):
tenancy/middleware.py)Identical to the GP app. When a PartnerOrg subdomain ({slug}.dental.veripath.co.uk) matches a PartnerOrg, the thread-local tenant is set to the client-sector Tenant (for database routing) if one exists; otherwise it falls back to the parent (dental) tenant. PartnerOrg is stored in thread-local via set_current_partner_org.
tenancy/routers.py)DENTAL_APPS routing now checks for client-sector tenants and routes to per-tenant database aliases (sector_client_{subdomain}), matching the GP_APPS pattern. allow_migrate for dental apps now also permits client database aliases so per-tenant migrations work.
All views that constructed DB aliases with f"sector_{tenant.sector.slug}" were updated to use get_tenant_db_alias(tenant):
appointments/views_config.py (15 occurrences)users/views.py (2 occurrences)