# Deploying PIG PIG is an API/web container, a private Piggy worker/chat container and 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 -> A www.primeintellectgrowth.com -> ``` ## 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 | | `PIGGY_INFERENCE_API_KEY` | Model credential held only by the Piggy process | | `PIGGY_INTERNAL_TOKEN` | A generated 32+ character bearer token shared only by API and Piggy | Optional: `PRIME_API_KEY` (scope it to `Availability → Read` only), `PIGGY_ENABLED`, and the Slack and Buzz credentials. Piggy listens on `piggy:8931` inside the Compose network. The port is exposed to other containers but never published to the host, and Caddy must not route to it. The CRM API authenticates the user, forwards only bounded chat context, and uses `PIGGY_INTERNAL_TOKEN` in an Authorization header. Never put that token in a query string, where proxies and access logs can retain it. ## 3. Start ```bash docker compose -p pig up -d db docker compose -p pig run --rm --no-deps app pnpm exec tsx packages/db/src/migrate.ts docker compose -p pig up -d --build app docker compose -p pig run --rm --no-deps app pnpm exec tsx packages/db/src/seed/index.ts # optional ``` **Migrate from a one-off container, before the app starts — not with `exec`.** `exec` needs a running app to attach to, and a release that queries a table its migration has not yet created crash-loops before you can attach to it. You then have a container restarting every few seconds and no way in. `run --rm --no-deps` uses the same image without the app, and without starting its dependencies twice. This is what commit d4d7095 changed and it is what `scripts/deploy.sh` does. When Piggy is enabled, start its private profile as well: ```bash docker compose -p pig --profile piggy up -d --build ``` 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
`, 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. **That hash exists in three places, and only two of them are checked.** | Copy | Checked by | |---|---| | `.gitea/workflows/ci.yml` (the `expected` constant) | itself, on every run | | `deploy/Caddyfile.example` | nothing — it is an example | | **the live `Caddyfile` on the host** | **nothing at all** | The live one is the only copy that decides whether a browser runs the script. Nothing in this repository can see it, CI cannot fail on it, and the failure is a white flash for dark-mode users with no error anywhere. Editing that script means editing all three by hand and reloading Caddy. ## 5. Verify ```bash curl -s https://primeintellectgrowth.com/api/health # {"ok":true,"service":"pig","version":"0.1.0"} ``` Check the **public origin**, not just `127.0.0.1:8920`. The `bind` failure above answers with a valid certificate, HTTP 200 and an empty body, which satisfies every check that only asks whether something responded. `scripts/deploy.sh` now asserts the body is non-empty and contains the application's mount point for this reason. ## Learn videos PIG hosts its own Learn videos. There is no video service to configure, no embed host and — because a native `