Stefan Lazic 973cb9efc6
CI / ci (push) Canceled after 0s
Update README.md
2026-09-06 15:54:44 +01:00
2026-08-02 17:47:23 +01:00
2026-08-02 17:47:23 +01:00
2026-08-07 17:30:12 +01:00
2026-08-02 17:47:23 +01:00
2026-08-05 09:25:59 +01:00
2026-09-05 21:03:36 +01:00
2026-09-06 12:17:10 +01:00
2026-09-05 23:32:54 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 09:32:34 +01:00
2026-09-06 12:17:10 +01:00
2026-09-05 14:03:15 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 13:39:55 +01:00
2026-09-06 12:17:10 +01:00
2026-09-06 12:17:10 +01:00
2026-09-05 13:39:55 +01:00
2026-09-06 10:38:21 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 09:32:30 +01:00
2026-09-05 13:39:55 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 09:32:05 +01:00
2026-09-05 09:32:05 +01:00
2026-09-06 15:54:44 +01:00
2026-08-02 17:47:23 +01:00
2026-09-05 13:39:55 +01:00
2026-09-05 13:39:55 +01:00
2026-09-05 13:39:55 +01:00

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

# 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 <your-repo-url> /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.

# 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)"
# 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

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.
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).

docker build -t nextchair-app:latest .

3. The management UI (Manager)

This is what you'll use to add every business from here on.

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.
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:

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://<your-manager-domain> 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://<business-domain>/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:

cd /opt/nextchair
git pull

Before rebuilding, check whether either example environment file has gained new settings since your last deployment:

# 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:

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:

cd /opt/nextchair/stacks/<slug>
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:

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.

S
Description
Booking app as an alternative to Booksy
Readme
2.6 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 89.4%
HTML 9.6%
CSS 0.3%
JavaScript 0.3%
Dockerfile 0.2%
Other 0.2%