c747eb2aa7
One container plus a Postgres behind any TLS-terminating proxy. Nothing is specific to a particular host. The app and API are served from a SINGLE origin. This is not tidiness: browser auth sessions live in per-origin storage, so splitting them across two hostnames makes sign-in loop in a way that presents as a server fault. The short alias redirects rather than serving a second origin. Two safety properties verified by running the image, not by reading the code: - With NODE_ENV=production and no SUPABASE_URL, the process refuses to start and says why. Serving the whole CRM unauthenticated is a worse outcome than failing to deploy, so the failure is deliberate and loud. - In production the development auth bypass does not apply: an unauthenticated request to /api/dashboard returns 401 rather than adopting the first user in the table. The Dockerfile typechecks all six packages as a build gate, so a deploy that does not compile fails at build time rather than in front of a user. Runtime runs unprivileged as `node`, and Postgres is not published to the host. Docs cover the ontology and why it is shaped this way, agent connection for Claude Code / Codex / prime-agent / Buzz, and the provenance rules governing seed data about real people — including how to have your record removed. Verified: image builds, container reports healthy, serves the SPA, enforces auth, and the production guard exits non-zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77 lines
2.4 KiB
Markdown
77 lines
2.4 KiB
Markdown
# Deploying PIG
|
|
|
|
PIG is one container plus a Postgres, behind any reverse proxy that terminates
|
|
TLS. Nothing here is specific to a particular host.
|
|
|
|
## 1. DNS
|
|
|
|
Point the apex and `www` at the machine. Both must resolve before the proxy can
|
|
obtain a certificate.
|
|
|
|
```
|
|
A primeintellectgrowth.com -> <your IP>
|
|
A www.primeintellectgrowth.com -> <your IP>
|
|
```
|
|
|
|
## 2. Configuration
|
|
|
|
```bash
|
|
cp .env.example .env # then edit
|
|
```
|
|
|
|
The values that must be set for a production start:
|
|
|
|
| Variable | Why |
|
|
|---|---|
|
|
| `POSTGRES_PASSWORD` | Generate a fresh one; never reuse another service's |
|
|
| `PIG_PUBLIC_URL` | The single origin the app is served from |
|
|
| `SUPABASE_URL` / `SUPABASE_ANON_KEY` | Authentication. The app refuses to start in production without a Supabase URL, because it would otherwise serve the whole CRM unauthenticated |
|
|
| `PIG_ADMIN_EMAILS` | Who may administer. **Every address here must already have an account** — an unregistered address listed as an admin is a standing offer of admin rights to whoever claims it first |
|
|
|
|
Optional: `PRIME_API_KEY` (scope it to `Availability → Read` only),
|
|
`PIGGY_ENABLED` + `ANTHROPIC_API_KEY`, and the Slack and Buzz credentials.
|
|
|
|
## 3. Start
|
|
|
|
```bash
|
|
docker compose -p pig up -d --build
|
|
docker compose -p pig exec app npx tsx packages/db/src/migrate.ts
|
|
docker compose -p pig exec app npx tsx packages/db/src/seed/index.ts # optional
|
|
```
|
|
|
|
Use `-p pig`. A compose project that shares a name with a neighbouring stack
|
|
will adopt its volumes, which is a memorable way to lose a database.
|
|
|
|
## 4. Reverse proxy
|
|
|
|
See `Caddyfile.example`. Serve the app and API from the **same** origin.
|
|
|
|
## 5. Verify
|
|
|
|
```bash
|
|
curl -s https://primeintellectgrowth.com/api/health
|
|
# {"ok":true,"service":"pig","version":"0.1.0"}
|
|
```
|
|
|
|
## Upgrading
|
|
|
|
```bash
|
|
git pull
|
|
docker compose -p pig up -d --build
|
|
docker compose -p pig exec app npx tsx packages/db/src/migrate.ts
|
|
```
|
|
|
|
Migrations are additive and safe to re-run; Drizzle tracks what has been
|
|
applied. Take a dump before a major upgrade anyway:
|
|
|
|
```bash
|
|
docker compose -p pig exec db pg_dump -U pig pig | gzip > pig-$(date +%F).sql.gz
|
|
```
|
|
|
|
## A note on the auth project
|
|
|
|
PIG verifies JWTs but authorizes from its own `users` table. If the Supabase
|
|
project is shared with another application, its users get **nothing** here until
|
|
they are explicitly invited. That is deliberate, and it is why a valid token can
|
|
still return `403 needs_profile`.
|