diff --git a/README.md b/README.md index 1b99ace..5bf2420 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ **Stack:** Node.js · Express · Sequelize (PostgreSQL) · JWT · Google OAuth 2.0 · Nodemailer **Deployment** DigitalOcean Droplet (3 Droplets for API + S3 and Postgres) +## Self-hosting +See [SELF_HOSTING.md](./SELF_HOSTING.md) for the full Docker Compose setup guide. + ## TODO Branchings and Tags (Backups) ``` diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md new file mode 100644 index 0000000..8f2c391 --- /dev/null +++ b/SELF_HOSTING.md @@ -0,0 +1,193 @@ +# Self-Hosting `new_starr` (Docker Compose) — Step-by-Step + +This stack bundles everything: PostgreSQL, Valkey (Redis), Garage (S3), the API, and the +frontend. Battle-tested end-to-end on a completely fresh Debian machine on 2026-08-29 — +every gotcha below actually happened once and is now fixed or documented. + +--- + +## 0. What you need + +- A Linux machine (Debian/Ubuntu commands below — adapt the package manager for others) +- Docker Engine + Compose plugin +- Node.js 22 + pnpm (only needed once, to build the frontend bundle) +- Git access to this repo + +--- + +## 1. Install Docker + +**Check your actual distro first** — Docker has separate repos for Debian and Ubuntu, +and mixing them up 404s: +```bash +cat /etc/os-release # check ID (debian/ubuntu) and VERSION_CODENAME +``` + +```bash +sudo apt-get update +sudo apt-get install -y ca-certificates curl gnupg +sudo install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg +sudo chmod a+r /etc/apt/keyrings/docker.gpg + +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +sudo apt-get update +sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +``` +(Swap `debian` → `ubuntu` in both URLs if you're actually on Ubuntu. If your codename is +brand-new and Docker's repo 404s, fall back to the previous stable codename — e.g. use +`bookworm` instead of `trixie`.) + +Let your user run Docker without `sudo`: +```bash +sudo usermod -aG docker $USER +newgrp docker +docker compose version # sanity check +``` + +--- + +## 2. Clone & install + +```bash +git clone git@github.com:rgrgogu/new_starr.git +cd new_starr +corepack enable && corepack prepare pnpm@11.3.0 --activate +pnpm install +``` + +--- + +## 3. Configure environment — **three** files, not two + +### `apps/api/.env` +```bash +cp apps/api/.env-production apps/api/.env +``` +Fill in every `CHANGE_ME`. For a **local bundled stack** (as opposed to CockroachDB +Cloud / managed S3), these specific values matter: +```env +DB_HOST=postgres +DB_PORT=5432 +DB_SSL=false # bundled Postgres has no TLS +CACHE_DRIVER=redis +REDIS_URL=redis://valkey:6379 +S3_ENDPOINT=http://garage:3900 +GARAGE_RPC_SECRET= +``` +Generate every secret independently: +```bash +node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" +``` + +### `apps/web/.env` +```bash +cd apps/web && cp .env.example .env +``` +Fill in `VITE_API_URL`, `VITE_APP_URL`, etc., then `cd ../..`. + +### Root `new_starr/.env` — easy to miss, causes silent failures +`docker-compose.yml`'s bundled `postgres` service reads `${DB_NAME}`, `${DB_USER}`, +`${DB_PASSWORD}` from a **root-level** `.env` — NOT from `apps/api/.env`. Create it +separately, with **exactly the same values** as the DB block you just put in +`apps/api/.env`: +```env +DB_NAME=starr +DB_USER=starr +DB_PASSWORD= +``` +⚠️ Postgres only reads these once, at first-ever container start, and bakes them into +its data volume. If the two files ever drift apart afterward, you'll get +`password authentication failed` even though everything "looks" configured — fix by +making `apps/api/.env` match what's actually in the volume (the root `.env` value at +the time Postgres first started), not the other way around. + +--- + +## 4. Build the frontend bundle *before* building images + +`apps/web/Dockerfile` only copies a pre-built `dist/` — it does not run the build itself: +```bash +pnpm --filter web build +``` + +--- + +## 5. Bring the stack up + +```bash +docker compose up -d --build +docker compose ps # all 5 services should show Up/healthy +``` + +--- + +## 6. One-time Garage (S3) cluster init + +Garage's image ships only a static binary — no shell — so this can't be scripted as a +container entrypoint; it's a manual one-time step: +```bash +docker compose exec garage /garage node id +``` +Copy the ID (the part **before** the `@host:port`), then: +```bash +docker compose exec garage /garage layout assign -z dc1 -c 100G +docker compose exec garage /garage layout apply --version 1 +docker compose exec garage /garage bucket create +docker compose exec garage /garage key import -n starr-app --yes +docker compose exec garage /garage bucket allow --read --write --owner --key +``` +(`S3_BUCKET`/`S3_ACCESS_KEY`/`S3_SECRET_KEY` are whatever you set in `apps/api/.env`.) + +--- + +## 7. Run database migrations + +```bash +docker compose exec backend sequelize-cli db:migrate +``` + +--- + +## 8. Verify + +```bash +docker compose ps +``` +- Frontend: **http://localhost:4650** +- Backend: http://localhost:3024/api + +If both respond, you're done. + +--- + +## Troubleshooting — real errors hit while writing this guide + +| Symptom | Cause | Fix | +|---|---|---| +| `apt` 404: `.../linux/ubuntu Release` not found | Docker repo added for the wrong distro (e.g. Ubuntu URL on a Debian box) | Match the URL to your real distro/codename from `/etc/os-release` | +| `permission denied ... docker.sock` | User not in the `docker` group | `sudo usermod -aG docker $USER && newgrp docker` | +| `WARN: "DB_NAME" variable is not set` (and similar) | Root `new_starr/.env` missing | Create it (see step 3) | +| `postgres` container stuck `Restarting` | `POSTGRES_PASSWORD` blank (same root `.env` issue) | Same fix as above | +| `exec: "/bin/sh": stat /bin/sh: no such file or directory` | Some images (like Garage's) ship no shell at all — never wrap their entrypoint in `/bin/sh` | Use the image's default command; do cluster setup via `docker exec ` directly (no shell needed) | +| Garage crash-loops: `rpc_secret value is missing` | `GARAGE_RPC_SECRET` not set in `apps/api/.env` | Set it, then `docker compose up -d --force-recreate garage` | +| `password authentication failed for user ...` | `apps/api/.env` and root `.env` DB passwords don't match exactly | Make them match; Postgres only honors whatever was set at first boot | +| Fixed a file but the same error keeps happening | Images bake in code at **build** time — there's no live volume mount | `docker compose up -d --build ` after any source change | +| `ERROR: Unknown constraint error` during migrate | Sequelize hides the real Postgres error behind this generic message | Run the raw SQL directly via `docker compose exec postgres psql -U -d ` to see the actual cause | +| Migration uses `STRING` as a raw SQL type | CockroachDB alias, not valid in real Postgres | Already fixed in this repo (swapped to `TEXT`, which works on both) | +| A service vanishes from `docker compose ps` | A previous `up` was scoped to one service (e.g. `up -d --build backend`), which doesn't touch others | Run a plain `docker compose up -d` (no service name) to restore everything | +| Browser: "Unable to connect" | Either the container isn't actually running, or you're browsing from a different machine than the one running Docker | Check `docker compose ps` first; `localhost` only resolves to the machine Docker is on | + +--- + +## Notes + +- Frontend is served on **`:4650`**, not `:80` — change `frontend.ports` in + `docker-compose.yml` if you want a different port. +- Cluster init for Garage is intentionally manual (step 6) — the old automated + `garage-init.sh` approach was removed because it depended on a shell that doesn't + exist in the Garage image. diff --git a/docker-compose.yml b/docker-compose.yml index 26caa38..c0d76f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: frontend: build: ./apps/web ports: - - "80:80" + - "4650:80" restart: unless-stopped # ── PostgreSQL ───────────────────────────────────────────────────────────────