1 file changed
+148
-28
@@ -4,48 +4,168 @@ Booking app as an alternative to Booksy
|
|||||||
|
|
||||||
## VPS deployment (multi-business)
|
## VPS deployment (multi-business)
|
||||||
|
|
||||||
One-time setup on a fresh VPS with Docker and the Compose plugin already
|
Everything below runs on a fresh VPS with Docker and the Compose plugin
|
||||||
installed. Run everything from the repo's checkout directory.
|
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
|
```bash
|
||||||
# 1. Clone the repo at the path you intend to keep it at — STACKS_DIR
|
# STACKS_DIR later on must be this exact absolute path — every stack
|
||||||
# below must be this exact absolute path, since containers shell
|
# shells `docker compose` back out to the HOST's dockerd via a mounted
|
||||||
# `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
|
||||||
# socket (see stacks/infra/compose.yaml and manager/README.md for why).
|
# it's cloned here and never moved.
|
||||||
git clone <your-repo-url> /opt/nextchair
|
git clone <your-repo-url> /opt/nextchair
|
||||||
cd /opt/nextchair
|
cd /opt/nextchair
|
||||||
|
```
|
||||||
|
|
||||||
# 2. Shared Docker network every stack attaches to.
|
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 \"console.log('MANAGER_PASSWORD_HASH=' + require('/tmp/gen/node_modules/bcryptjs').hashSync('your-password', 12))\""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1. Shared infrastructure — network, MySQL, Caddy, Dockge
|
||||||
|
|
||||||
|
```bash
|
||||||
docker network create next-chair-edge
|
docker network create next-chair-edge
|
||||||
|
|
||||||
# 3. Shared infrastructure: MySQL, Caddy (reverse proxy + automatic TLS),
|
|
||||||
# Dockge (stack management UI, http://<vps-ip>:5001).
|
|
||||||
cp .env.infra.example stacks/infra/.env
|
cp .env.infra.example stacks/infra/.env
|
||||||
# edit stacks/infra/.env — MYSQL_ROOT_PASSWORD, ACME_EMAIL, and set
|
```
|
||||||
# STACKS_DIR to this exact clone path, e.g. /opt/nextchair/stacks
|
|
||||||
|
Edit `stacks/infra/.env`:
|
||||||
|
- `MYSQL_ROOT_PASSWORD` — the first secret you generated above.
|
||||||
|
- `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
|
docker compose -f stacks/infra/compose.yaml --env-file stacks/infra/.env up -d
|
||||||
|
```
|
||||||
|
|
||||||
# 4. Build the shared business image (re-run this after every code
|
### 2. Build the shared application image
|
||||||
# update — see stacks/blake-heritage/compose.yaml's own note).
|
|
||||||
|
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 .
|
docker build -t nextchair-app:latest .
|
||||||
|
```
|
||||||
|
|
||||||
# 5. The management UI (manager/) — this is what you'll use to add
|
### 3. The management UI (Manager)
|
||||||
# businesses from here on, not the by-hand steps in stacks/infra/
|
|
||||||
# compose.yaml's header (those exist only as a fallback/reference).
|
This is what you'll use to add every business from here on.
|
||||||
|
|
||||||
|
```bash
|
||||||
cp manager/.env.example stacks/manager/.env
|
cp manager/.env.example stacks/manager/.env
|
||||||
# edit stacks/manager/.env — MANAGER_EMAIL/PASSWORD_HASH, MANAGER_SESSION_SECRET,
|
```
|
||||||
# MYSQL_ROOT_PASSWORD (same value as step 3), and STACKS_DIR (same as step 3)
|
|
||||||
|
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
|
docker compose -f stacks/manager/compose.yaml --env-file stacks/manager/.env up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, per business, point its domain's DNS at the VPS and use the
|
Point your chosen Manager domain's DNS (A/AAAA record) at the VPS, then
|
||||||
management UI (`https://<your-manager-domain>` — add a Caddy site for it
|
give it a Caddy site and reload:
|
||||||
the same way as any business, see `stacks/infra/sites/.gitkeep`) to add
|
|
||||||
it: name, slug, domain — it creates the database, generates secrets,
|
|
||||||
starts the container, and runs migrations for you. Finish setup by
|
|
||||||
visiting `https://<business-domain>/install`.
|
|
||||||
|
|
||||||
To ship a code update to every business: `git pull`, rebuild the image
|
```bash
|
||||||
(step 4), then use each business's **Update** button in the management UI
|
cat > stacks/infra/sites/manager.caddy << 'EOF'
|
||||||
(or `docker compose up -d --force-recreate` in its `stacks/<slug>/`
|
manager.example.com {
|
||||||
directory) to recreate it against the new image.
|
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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/nextchair
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Rebuild the one shared image every business uses.
|
||||||
|
docker build -t nextchair-app:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, for **each** business: use its **Update** button in the Manager UI,
|
||||||
|
or run this directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd 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/`), update it the same way — this one does rebuild, since unlike
|
||||||
|
a business it doesn't share a prebuilt image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/nextchair
|
||||||
|
docker compose -f stacks/manager/compose.yaml --env-file stacks/manager/.env up -d --build
|
||||||
|
```
|
||||||
Reference in new issue
Block a user