Part A — Round 3 regressions: - A.1: verify-cost-reconciliation.mjs now loads .env.local, wraps in try/catch, exits 1 on any failure - A.2: Vehicle Costs restored Santam — back to R 14,996.31 (matches row-by-row sum) - A.3: incl<excl / VAT math / VAT>20% now FATAL — seed exits 2 with full report (4 anomalies on Charl-CCS + Buildsmart) - A.4: Reconciliation target R 694,962.93 with both deltas itemised (Personnel +R19,800, Santam -R5,322.44, unexplained R 0.00) - A.5: 'Lines' view uses actual lines (recharts LineChart), no fills; fills only in 'Cost stack' Part B — Mobile standard: - B.1: header wraps to 2 rows on mobile; YTD badge cards stack; page-wide overflow eliminated on /income, /charts, /overheads, /running, /expenses - B.2: IncomeView card row layout stacked — name gets its own line on mobile, never truncated to single char - B.3: Plant type filter is a <select> dropdown below lg (was 18 pills wrapping to 13 rows); same for sort on /income and /running - B.4: Plant table first column sticky; -webkit-overflow-scrolling: touch; tables scroll inside their own container - B.5: All buttons/links use min-h-[44px] tap targets; text below 12px is bumped where it matters - B.6: Charts legend wraps below title; chart min-height h-44 → h-52 on mobile - B.7: paddingTop env(safe-area-inset-top) on sticky header for notch/home indicator clearance AGENTS.md created with the standing mobile checklist rule.
97 lines
3.6 KiB
Markdown
97 lines
3.6 KiB
Markdown
# Deploying Mega Plant to Coolify
|
|
|
|
## The one thing that catches everyone
|
|
|
|
`NEXT_PUBLIC_PB_URL` must be set as a **Build Argument**, not only as an
|
|
environment variable.
|
|
|
|
Next.js compiles `NEXT_PUBLIC_*` values into the JavaScript the browser
|
|
downloads. That happens at `npm run build`, inside the Docker build. Setting the
|
|
value only as a runtime env var means the server sees the right URL while the
|
|
browser bundle still contains whatever was baked in at build time.
|
|
|
|
The Dockerfile fails the build with an explicit message if the arg is missing,
|
|
so you'll find out immediately rather than after a confusing deploy.
|
|
|
|
## Setup
|
|
|
|
**1. Create the application**
|
|
|
|
Coolify → Project → New Resource → **Application** → point at this repository.
|
|
Build Pack: **Dockerfile**. Port: **3000**.
|
|
|
|
**2. Build Arguments** (Build → Build Arguments)
|
|
|
|
| Name | Value |
|
|
| --- | --- |
|
|
| `NEXT_PUBLIC_PB_URL` | `https://pb.mega.plant.example` |
|
|
|
|
**3. Environment Variables** (runtime — Environment Variables tab)
|
|
|
|
| Name | Value | Notes |
|
|
| --- | --- | --- |
|
|
| `NEXT_PUBLIC_PB_URL` | same as above | server-side reads use it too |
|
|
| `PB_ADMIN` | the PocketBase superuser email | server-only, never exposed |
|
|
| `PB_PASS` | the superuser password | server-only, never exposed |
|
|
| `PORT` | `3000` | optional, defaults to 3000 |
|
|
|
|
**4. Health check**
|
|
|
|
Coolify → Health Checks → path `/api/health`, port `3000`. The Dockerfile also
|
|
declares its own `HEALTHCHECK` against the same endpoint.
|
|
|
|
The probe deliberately does not touch PocketBase — it answers "is the web server
|
|
up?". If it also checked the database, a brief PocketBase outage would make
|
|
Coolify restart a perfectly healthy container.
|
|
|
|
**5. Domain and TLS**
|
|
|
|
Set the FQDN in Coolify and let Traefik issue the certificate. **HTTPS is
|
|
required** — the PWA will not install over plain HTTP, and the service worker
|
|
won't register.
|
|
|
|
## PocketBase must be on HTTPS too
|
|
|
|
Server-side rendering reaches PocketBase from inside the container, so an
|
|
`http://` URL works there. But the moment any browser-side code calls
|
|
PocketBase from an HTTPS page, the request is blocked as mixed content.
|
|
|
|
Put PocketBase behind TLS. If it's another Coolify resource, give it a domain
|
|
and let Traefik handle the certificate, the same as this app.
|
|
|
|
## Before the first deploy
|
|
|
|
- [ ] **Rotate the PocketBase superuser password.** The old one
|
|
(`plant@megainfra.co.za` / `1234567890`) was committed to this repository
|
|
in an early version of `scripts/seed.mjs`. Treat it as compromised.
|
|
- [ ] Confirm `.env.local` is not committed (it's in `.gitignore`).
|
|
- [ ] Run `npm run typecheck` and `npm run build` locally first — a Docker build
|
|
failure costs several minutes to discover.
|
|
|
|
## Test the image locally
|
|
|
|
```bash
|
|
docker build --build-arg NEXT_PUBLIC_PB_URL=https://pb.mega.plant.example -t mega-plant .
|
|
```
|
|
|
|
```bash
|
|
docker run --rm -p 3000:3000 -e PB_ADMIN=you@example.com -e PB_PASS=secret mega-plant
|
|
```
|
|
|
|
Then check the health endpoint:
|
|
|
|
```bash
|
|
curl -s localhost:3000/api/health
|
|
```
|
|
|
|
## Notes
|
|
|
|
- **`--webpack` is not optional.** `@ducanh2912/next-pwa` is Webpack-only and
|
|
generates `public/sw.js`. `package.json` already passes the flag in `build`;
|
|
don't remove it or the PWA silently stops working.
|
|
- **`data/source/` is excluded from the image** (~2MB of workbook and extracted
|
|
JSON). It's only needed by `npm run seed`, which is a one-off you run locally
|
|
against PocketBase — not something the container does.
|
|
- **Seeding is not part of deployment.** The container never writes to
|
|
PocketBase on boot. Run `npm run seed` deliberately, from your machine.
|
|
- The image runs as a non-root `nextjs` user on Node 20 Alpine.
|