# NextChair Booking app as an alternative to Booksy ## VPS deployment (multi-business) Everything below runs on a fresh VPS with Docker and the Compose plugin already installed — no other prerequisite (no host Node.js/npm needed; every command either uses Docker itself or plain shell tools). Every code block is meant to be pasted as-is, in order. ### 0. One-time: clone the repo and generate your secrets ```bash # STACKS_DIR later on must be this exact absolute path — every stack # shells `docker compose` back out to the HOST's dockerd via a mounted # socket, so the path only resolves to the same thing on both sides if # it's cloned here and never moved. git clone /opt/nextchair cd /opt/nextchair ``` Generate the secrets you'll paste into the `.env` files in the steps below. Run these now and keep the output somewhere safe (a password manager) — you'll need each value again in a moment. ```bash # MYSQL_ROOT_PASSWORD — goes into BOTH stacks/infra/.env and manager/.env # (the same value in both places). echo "MYSQL_ROOT_PASSWORD=$(openssl rand -hex 24)" # MANAGER_SESSION_SECRET — manager/.env only. echo "MANAGER_SESSION_SECRET=$(openssl rand -hex 32)" # MANAGER_SETTINGS_ENCRYPTION_KEY — manager/.env only. Encrypts the # SMTP/VAPID secrets you'll enter in the Manager's Settings page later. # Losing/rotating this makes any already-saved secret there undecryptable, # so store it as carefully as the other secrets on this page. echo "MANAGER_SETTINGS_ENCRYPTION_KEY=$(openssl rand -hex 32)" ``` ```bash # MANAGER_PASSWORD_HASH — manager/.env only. Replace "your-password" with # your chosen Manager login password first. Run via a throwaway container # so this works with nothing but Docker installed on the host. docker run --rm node:22-alpine sh -c \ "mkdir -p /tmp/gen && cd /tmp/gen && npm install bcryptjs --no-audit --no-fund >/dev/null 2>&1 && \ node -e \"const h=require('/tmp/gen/node_modules/bcryptjs').hashSync('your-password',12); console.log('MANAGER_PASSWORD_HASH=\\\"'+h.replace(/\\$/g,'\\$\\$')+'\\\"')\"" ``` ### 1. Shared infrastructure — network, MySQL, Caddy, Dockge ```bash docker network create next-chair-edge cp .env.infra.example stacks/infra/.env ``` Edit `stacks/infra/.env`: - `MYSQL_ROOT_PASSWORD` — the first secret you generated above. - `MYSQL_HOST_PORT` — set this to `127.0.0.1:3306` so MySQL is only exposed on the VPS loopback interface, not publicly. You can still connect to it remotely through an SSH tunnel. - `ACME_EMAIL` — a real address you check (Let's Encrypt renewal notices). - `STACKS_DIR` — the absolute clone path, e.g. `/opt/nextchair/stacks`. ```bash docker compose -f stacks/infra/compose.yaml --env-file stacks/infra/.env up -d ``` ### 2. Build the shared application image Every business (and no other step in this guide) runs this same image — you only rebuild it here, once, and again whenever you ship a code update (see "Updating" below). ```bash docker build -t nextchair-app:latest . ``` ### 3. The management UI (Manager) This is what you'll use to add every business from here on. ```bash cp manager/.env.example stacks/manager/.env ``` Edit `stacks/manager/.env`: - `MANAGER_EMAIL` — the email you'll log in with. - `MANAGER_PASSWORD_HASH` — the bcrypt hash generated in step 0. - `MANAGER_SESSION_SECRET` and `MANAGER_SETTINGS_ENCRYPTION_KEY` — the two other values generated in step 0. - `MANAGER_PUBLIC_URL` — `https://` + the domain you'll point at this app in the next step. - `MYSQL_ROOT_PASSWORD` — same value as `stacks/infra/.env`. - `STACKS_DIR` — same absolute path as step 1. ```bash docker compose -f stacks/manager/compose.yaml --env-file stacks/manager/.env up -d --build ``` Point your chosen Manager domain's DNS (A/AAAA record) at the VPS, then give it a Caddy site and reload: ```bash cat > stacks/infra/sites/manager.caddy << 'EOF' manager.example.com { reverse_proxy next-chair-manager:3000 } EOF docker exec next-chair-caddy caddy reload --config /etc/caddy/Caddyfile ``` (Replace `manager.example.com` with your real domain — Caddy issues its TLS certificate automatically on first request.) ### 4. Configure platform-wide settings (once) Log into the Manager at `https://` and open **Settings**. Both sections are optional to fill in now, but doing it before adding your first business means that business never needs its own SMTP/push setup: - **Email / SMTP** — your SMTP relay's host/port/username/password and a from-address. Use **Send Test Email** to confirm it before saving for real, then **Save Changes** to apply it to every business. - **Push Notifications** — click **Generate VAPID Keys** once (a contact email is enough to start). This is a one-time, platform-wide key pair — see that section's own copy for what happens if you ever regenerate it. ### 5. Add your first business Still in the Manager: **Add Business** → name, slug, domain. This creates its database, generates its secrets, starts its container, and runs its migrations for you — no manual per-business `.env` editing needed. Point that business's own domain's DNS at the VPS (the Manager doesn't do this for you), then finish its setup by visiting `https:///install`. Repeat this step for each additional business, whenever you want one. ## Updating after a code change For an existing NextChair server, **do not re-run the fresh VPS setup above** and do not overwrite your production `.env` files. First pull the latest code: ```bash cd /opt/nextchair git pull ``` Before rebuilding, check whether either example environment file has gained new settings since your last deployment: ```bash # Review only — do not copy these files over your existing production .env files. diff -u stacks/infra/.env .env.infra.example || true diff -u stacks/manager/.env manager/.env.example || true ``` If a new variable has been added, copy **only that variable** into the corresponding production `.env` and give it an appropriate value. Keep all existing passwords, secrets and generated values unchanged. Then rebuild the one shared image every business uses: ```bash cd /opt/nextchair docker build -t nextchair-app:latest . ``` Then, for **each** business: use its **Update** button in the Manager UI, or run this directly: ```bash cd /opt/nextchair/stacks/ docker compose --env-file .env up -d --force-recreate ``` Either way this recreates that business's container against the freshly built image — and its own startup automatically runs `prisma migrate deploy`, so any new database migration is applied on the same step. No separate migration command needed. If the code change touched the **Manager app itself** (anything under `manager/`), rebuild/recreate the Manager too: ```bash cd /opt/nextchair docker compose -f stacks/manager/compose.yaml --env-file stacks/manager/.env up -d --build ``` If both the business app and Manager changed, do both update steps.