Direct VPS Deployment
This document describes the current active direct-VPS deployment path for CareConnect.
Shared-VPS ownership note:
- This document is canonical for the CareConnect direct-VPS runtime shape.
- Shared host topology, ingress ownership, service inventory, and other cross-project VPS facts are canonical in
/home/jer/repos/platform-ops. - Use
/home/jer/repos/platform-ops/PLAT-009-shared-vps-documentation-boundary.mdas the default boundary reference.
Current state:
- the app is packaged as a Docker container,
- the container is running successfully on the Hetzner VPS,
- the public host is
https://careconnect.ing, www.careconnect.ingredirects to the apex,- the container binds privately at
127.0.0.1:3300, GET /api/v1/healthreturns healthy on the VPS and publicly,- the deployed health payload reports the staged release revision,
- public boot degrades safely when optional Supabase or OneSignal browser config is absent.
Important naming note:
- the public product/domain identity is
CareConnect/careconnect.ing, - the live VPS runtime identifiers are
careconnect-web.
This is the active production path. The historical Vercel path remains documented in legacy-vercel.md only.
Runtime Shape
The deployment uses:
- Docker on the host VPS,
- host-managed Caddy for public ingress,
- loopback-only bind on the host (
127.0.0.1:3300 -> container:3000), - the Next.js standalone production output from
npm run build.
Local Prerequisites
Before copying anything to the VPS:
npm run lintnpm run type-checknpm run build- ensure the env contract is known from
.env.example
Do not use Playwright locally for this proof step.
Packaging Files
The direct VPS proof uses:
The deploy script expects exactly one argument:
It will:
- build a tagged image,
- prefer
docker buildx buildwhen available and fall back to legacydocker buildonly ifbuildxis missing, - replace the existing
careconnect-webcontainer if present, - run it with
--restart unless-stopped, - publish
127.0.0.1:3300:3000, - pass required
NEXT_PUBLIC_*values into both the image build and container runtime, - set
APP_VERSIONso/api/v1/healthreports the deployed revision, - print the expected health URL.
From a local workstation, you can also stage and optionally deploy a committed release in one step:
Verified Production State
As of 2026-03-11, the deployment has been verified with:
docker psshowingcareconnect-webhealthy on the VPS,curl -fsS http://127.0.0.1:3300/api/v1/health,curl -sS -D - "http://127.0.0.1:3300/api/v1/services?limit=1".curl -fsS https://careconnect.ing/api/v1/health,curl -fsS https://careconnect.ing/robots.txt,curl -fsS https://careconnect.ing/sitemap.xml.
Operational notes from the March 11, 2026 VPS stabilization work:
NEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYmust be present at image build time, not just container runtime.- The homepage should stay up even if those public Supabase values are absent from the browser bundle.
- OneSignal must remain disabled unless
NEXT_PUBLIC_ONESIGNAL_APP_IDis explicitly set. - A brief
degradedor connection-reset health response can occur during container replacement; repeat health checks until they stabilize.
Public ingress now runs through host Caddy:
www.careconnect.ing {
redir https://careconnect.ing{uri} 308
}
careconnect.ing {
encode zstd gzip
reverse_proxy 127.0.0.1:3300
}
Expected Health Check
After the container starts, verify:
Expected outcome:
- HTTP success,
- JSON response,
versionmatches the deployed git SHA or release revision,- no container crash loop.
Supabase RLS Repair Note
The initial private proof exposed a live Supabase row-level security recursion bug:
organization_memberspolicies queriedorganization_membersdirectly,servicespolicies also queriedorganization_members,- live reads failed with
42P17 infinite recursion detected in policy for relation "organization_members".
The approved minimal repair was:
- add helper functions:
public.is_org_member(uuid)public.is_org_admin(uuid)public.can_manage_org_services(uuid)- replace only the
organization_membersandservicespolicies to use those helpers.
The repo-tracked SQL artifact for that repair is:
This repair should be treated as part of the private-proof source of truth because the app could not pass health checks without it.
Current Limits
This document records the active deployment baseline.
Follow-up documentation still needed:
- broader historical cleanup for legacy references that do not affect the live runtime,
- any future status-page or subdomain policy.
For the current deploy/verify/rollback checklist, use: