93818a2d2c
The deployment came up healthy, served a valid certificate, and returned 200 — and was completely unreachable. Two distinct causes, both invisible from inside the host: 1. Every other site on this proxy binds to a private VNIC address. Caddy groups site blocks into servers BY listen address, so a block without `bind` landed in a separate server on :443. The specific listener wins for traffic arriving on that address, which is all public traffic after NAT, so requests hit the server that had never heard of these hostnames and fell through to an empty 200. Testing from the host with --resolve 127.0.0.1 worked perfectly, which is exactly why this was worth chasing from a third machine instead of trusting a local check. 2. The CSP blocked the inline pre-paint theme script, so dark-mode users would have seen a white flash on every load. Fixed with the script's hash rather than 'unsafe-inline', which would have defeated the policy, and rather than an external file, which would have reintroduced the flash. Editing that script changes its hash and silently breaks it, so that is written down. Verified from an independent host: health returns JSON, the app serves, an unauthenticated API call is refused, the short alias redirects, security headers are present, and the existing sites on the proxy are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
92 lines
3.2 KiB
Markdown
92 lines
3.2 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.
|
|
|
|
Two things that will otherwise cost you an hour:
|
|
|
|
- **If other sites on the host use `bind <address>`, yours must too.** Caddy
|
|
groups site blocks into servers by listen address. A block without `bind`
|
|
lands in a *separate* server on `:443`, and the more specific listener wins
|
|
for traffic arriving on that address — which is all public traffic after NAT.
|
|
The symptom is a valid certificate, a 200 response, an empty body, and none
|
|
of your headers. It looks like the app is broken; it is that the request
|
|
never reached it.
|
|
|
|
- **The CSP must carry the hash of the inline theme script** in `index.html`.
|
|
That script sets light or dark before first paint so dark-mode users do not
|
|
get a white flash. Editing it changes the hash and CSP will silently block
|
|
it — the browser console prints the hash it expects.
|
|
|
|
## 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`.
|