Operations
Runbook 05 — Platform apps (backend, CRM, portal)
Domains, Coolify apps, the auth flow, deploy steps, and the real gotchas.
Domains -> apps
| Domain | App | Role |
|---|---|---|
| soconnective.com | apps/web | Public website |
| backend.soconnective.com (also cms.) | apps/cms (Payload) | Engine: data, API, auth, multi-tenant, CMS, connections |
| crm.soconnective.com | apps/crm | Agency CRM (Next.js + shadcn/ui), consumes the backend API |
| portal.soconnective.com | apps/portal (future) | Client portal |
| dashboard.soconnective.com | legacy PHP | Retires when the CRM is complete |
Coolify apps
- Each app is a Coolify application:
base_directory= itsapps/<name>folder, build pack nixpacks, port 3000, deployed from Forgejomain. - CRM env:
PAYLOAD_URL = https://backend.soconnective.com(used for server-side fetches). - Payload (
apps/cms) serves bothbackend.andcms.;serverURL/CORS/CSRF include backend., cms. and crm. - Payload root
/redirects to/admin(vianext.configredirects).
Auth (CRM <-> backend)
- The team signs in at
crm./login-> a server action POSTs tobackend /api/users/login-> the returned token is stored in an httpOnly cookie on the crm domain. - Server components fetch the backend API with
Authorization: JWT <token>(server-to-server, no CORS needed). - Mutations run through server actions (which hold the token). A middleware guards all routes (redirects to
/loginwithout a token).
Deploy
- Push to
main-> the docs site auto-deploys (Forgejo webhook). Other apps deploy on demand: GET {coolify}/api/v1/deploy?uuid=<app-uuid>&force=true(token infs-secrets).
Gotchas (real)
- Git URL mangled to SSH when creating an app via the API -> clone fails (
does not appear to be a git repository). Fix: PATCHgit_repositoryto the fullhttps://feedback:<token>@git.soconnective.com/feedback/soconnective.git. - OOM during build (the "Collecting build traces" step) on the 8 GB VPS under load -> retry the deploy (usually succeeds on the 2nd attempt). Long-term: more swap or a dedicated build.
- TLS cert: after a new subdomain's DNS points to the VPS, restart the app so Traefik requests the Let's Encrypt certificate.
- DNS: new subdomains must have an A record ->
216.250.119.216(the VPS). An old Plesk box (216.250.126.64) still answers stale records with an expired certificate.