Attempt 2 of a management ui.
This commit is contained in:
54 files changed
+8804
-267
No files matched your search
@@ -0,0 +1,55 @@
|
||||
name: next-chair-blake-heritage
|
||||
|
||||
# One business's app container on the shared multi-business VPS setup —
|
||||
# see stacks/infra/compose.yaml for the shared MySQL/Caddy/Dockge this
|
||||
# attaches to, and that file's header for the exact one-time setup steps
|
||||
# (create this business's database, copy .env.business.example, add a
|
||||
# Caddy site file) needed before this file can start.
|
||||
#
|
||||
# docker compose -f stacks/blake-heritage/compose.yaml --env-file stacks/blake-heritage/.env up -d
|
||||
#
|
||||
# `stacks/marios/compose.yaml` is the identical shape for the second
|
||||
# business — this is also the shape the management UI (manager/)
|
||||
# generates for every new business it provisions.
|
||||
#
|
||||
# Uses the shared prebuilt image (see the repo root's README/Dockerfile —
|
||||
# `docker build -t nextchair-app:latest .` once per code update) rather
|
||||
# than building from source per business, so adding or updating a
|
||||
# business is just starting/recreating a container, not a rebuild.
|
||||
|
||||
services:
|
||||
next-chair-blake-heritage:
|
||||
image: nextchair-app:latest
|
||||
container_name: next-chair-blake-heritage
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- next-chair-media-blake-heritage:/app/storage/media
|
||||
healthcheck:
|
||||
# No curl/wget in the node:22-alpine base image — a plain Node
|
||||
# request avoids adding a package just for this. See
|
||||
# src/app/api/health/route.ts: healthy as soon as the database is
|
||||
# reachable, even before installation completes.
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"require('http').get('http://localhost:3000/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
networks:
|
||||
- next-chair-edge
|
||||
|
||||
volumes:
|
||||
next-chair-media-blake-heritage:
|
||||
name: next-chair-media-blake-heritage
|
||||
|
||||
networks:
|
||||
next-chair-edge:
|
||||
name: next-chair-edge
|
||||
external: true
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
# Let's Encrypt sends renewal/expiry notices here.
|
||||
email {$ACME_EMAIL}
|
||||
}
|
||||
|
||||
# One file per business in sites/ (see sites/.gitkeep) — Caddy doesn't
|
||||
# watch this directory for changes on its own, so adding/removing a file
|
||||
# needs `docker exec next-chair-caddy caddy reload --config
|
||||
# /etc/caddy/Caddyfile` (see stacks/infra/compose.yaml's header) to take
|
||||
# effect. The management UI (manager/) automates writing these files and
|
||||
# reloading.
|
||||
import sites/*.caddy
|
||||
@@ -0,0 +1,136 @@
|
||||
name: next-chair-infra
|
||||
|
||||
# Shared infrastructure for the multi-business VPS deployment: Caddy
|
||||
# (reverse proxy + automatic TLS via Let's Encrypt), one MySQL server
|
||||
# shared by every business (a separate database + user per business, NOT
|
||||
# one shared schema — see step 1 below), and Dockge (a web UI for
|
||||
# starting/stopping/inspecting these compose stacks by hand).
|
||||
#
|
||||
# Lives in stacks/infra/ so Dockge (which expects one directory per
|
||||
# stack, each with its own compose.yaml) can manage this stack too —
|
||||
# see stacks/blake-heritage/, stacks/marios/ for the same shape per
|
||||
# business. Deliberately separate from docker-compose.local.yaml at the
|
||||
# repo root, which is the single-business local/Docker-Desktop setup and
|
||||
# stays untouched by any of this.
|
||||
#
|
||||
# ── One-time setup ───────────────────────────────────────────────────────
|
||||
# docker network create next-chair-edge
|
||||
# cp .env.infra.example stacks/infra/.env (then fill in real values —
|
||||
# in particular STACKS_DIR must be this repo's absolute path on the
|
||||
# VPS, e.g. /opt/nextchair/stacks — see the STACKS_DIR comment in that
|
||||
# file for why it can't just be a relative path here)
|
||||
# docker compose -f stacks/infra/compose.yaml --env-file stacks/infra/.env up -d
|
||||
#
|
||||
# ── Per business ──────────────────────────────────────────────────────
|
||||
# Use the management UI (manager/) once it's running — it automates all
|
||||
# of the steps below. To do it by hand instead:
|
||||
# 1. Create that business's database + user on the shared MySQL server.
|
||||
# There's no automatic way to do this per business via env vars —
|
||||
# the mysql:8.0 image's MYSQL_DATABASE/USER/PASSWORD vars only
|
||||
# provision ONE database at first boot, and /docker-entrypoint-
|
||||
# initdb.d scripts only ever run once against an empty data
|
||||
# directory — so this one step is manual, run once per business:
|
||||
# docker exec -it next-chair-mysql mysql -uroot -p
|
||||
# CREATE DATABASE blake_heritage CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
# CREATE USER 'blake_heritage'@'%' IDENTIFIED BY 'choose-a-strong-password';
|
||||
# GRANT ALL PRIVILEGES ON blake_heritage.* TO 'blake_heritage'@'%';
|
||||
# FLUSH PRIVILEGES;
|
||||
# 2. Copy .env.business.example to stacks/<slug>/.env, fill it in with
|
||||
# the credentials just created and that business's domain.
|
||||
# 3. Add stacks/infra/sites/<slug>.caddy (see that directory's own
|
||||
# note) and reload Caddy:
|
||||
# docker exec next-chair-caddy caddy reload --config /etc/caddy/Caddyfile
|
||||
# 4. docker compose -f stacks/<slug>/compose.yaml --env-file stacks/<slug>/.env up -d
|
||||
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: next-chair-caddy
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
# Overridable for local testing (a machine already running
|
||||
# something else on 80/443) — a real deployment leaves these at
|
||||
# their defaults, since Let's Encrypt's HTTP challenge and normal
|
||||
# HTTPS traffic both expect the standard ports.
|
||||
- "${CADDY_HTTP_PORT:-80}:80"
|
||||
- "${CADDY_HTTPS_PORT:-443}:443"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
# One .caddy file per business, imported by the Caddyfile — see
|
||||
# sites/.gitkeep's own note. Adding/removing a file here needs a
|
||||
# `caddy reload` (see the per-business setup steps above) to take
|
||||
# effect; Caddy doesn't watch this directory on its own.
|
||||
- ./sites:/etc/caddy/sites
|
||||
# Caddy's own automatic-HTTPS certificate storage — must persist
|
||||
# across restarts or every restart re-issues every certificate
|
||||
# against Let's Encrypt's rate limits.
|
||||
- next-chair-caddy-data:/data
|
||||
- next-chair-caddy-config:/config
|
||||
networks:
|
||||
- next-chair-edge
|
||||
|
||||
next-chair-mysql:
|
||||
image: mysql:8.0
|
||||
container_name: next-chair-mysql
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
# No MYSQL_DATABASE/MYSQL_USER/MYSQL_PASSWORD here deliberately — this
|
||||
# server hosts one database per business (see step 1 above), not one
|
||||
# shared schema. Only MYSQL_ROOT_PASSWORD (in .env) is required for
|
||||
# the image to boot.
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_ai_ci
|
||||
ports:
|
||||
# Host-machine access (e.g. a GUI client over an SSH tunnel) — every
|
||||
# business's app container reaches this over the shared Docker
|
||||
# network via the service name `next-chair-mysql`, never this.
|
||||
- "${MYSQL_HOST_PORT:-3306}:3306"
|
||||
volumes:
|
||||
- next-chair-mysql-data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-change-me-too}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
networks:
|
||||
- next-chair-edge
|
||||
|
||||
dockge:
|
||||
image: louislam/dockge:1
|
||||
container_name: next-chair-dockge
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
DOCKGE_STACKS_DIR: "${STACKS_DIR}"
|
||||
ports:
|
||||
- "${DOCKGE_HOST_PORT:-5001}:5001"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- next-chair-dockge-data:/app/data
|
||||
# MUST be mounted at the identical absolute path on both host and
|
||||
# container — see the STACKS_DIR comment in .env.infra.example for
|
||||
# why (Dockge shells `docker compose` out to the HOST's dockerd via
|
||||
# the socket above, so a compose-file path only means the same
|
||||
# thing on both sides if the bind-mount doesn't change it).
|
||||
- "${STACKS_DIR}:${STACKS_DIR}"
|
||||
networks:
|
||||
- next-chair-edge
|
||||
|
||||
volumes:
|
||||
next-chair-mysql-data:
|
||||
name: next-chair-mysql-data
|
||||
next-chair-caddy-data:
|
||||
name: next-chair-caddy-data
|
||||
next-chair-caddy-config:
|
||||
name: next-chair-caddy-config
|
||||
next-chair-dockge-data:
|
||||
name: next-chair-dockge-data
|
||||
|
||||
networks:
|
||||
next-chair-edge:
|
||||
name: next-chair-edge
|
||||
external: true
|
||||
@@ -0,0 +1,13 @@
|
||||
Keeps this otherwise-empty directory tracked in git.
|
||||
|
||||
Each business gets its own <slug>.caddy file here, e.g.:
|
||||
|
||||
blake-heritage.example.com {
|
||||
reverse_proxy next-chair-blake-heritage:3000
|
||||
}
|
||||
|
||||
Generated by the management UI (manager/) when a business is added, or
|
||||
written by hand for the by-hand setup steps in stacks/infra/compose.yaml's
|
||||
header comment. Real *.caddy files are gitignored (deployment-specific,
|
||||
not something to hand-author into the repo) — only this placeholder is
|
||||
tracked.
|
||||
@@ -0,0 +1,39 @@
|
||||
name: next-chair-manager
|
||||
|
||||
# The management UI (see manager/ at the repo root — its own README
|
||||
# explains why it's a fully separate app). Its own stack, separate from
|
||||
# stacks/infra/compose.yaml, since this is real application code that
|
||||
# gets rebuilt/updated far more often than the off-the-shelf infra
|
||||
# images (MySQL, Caddy, Dockge) — a code change here shouldn't require
|
||||
# touching the infra stack at all.
|
||||
#
|
||||
# cp manager/.env.example stacks/manager/.env (then fill in real values)
|
||||
# docker compose -f stacks/manager/compose.yaml --env-file stacks/manager/.env up -d --build
|
||||
#
|
||||
# Requires stacks/infra/compose.yaml to already be running (shared MySQL,
|
||||
# Caddy, network). Add a Caddy site for it the same way as any business —
|
||||
# see stacks/infra/sites/.gitkeep — pointed at `next-chair-manager:3000`.
|
||||
|
||||
services:
|
||||
manager:
|
||||
build:
|
||||
context: ../../manager
|
||||
dockerfile: Dockerfile
|
||||
container_name: next-chair-manager
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# MUST be mounted at the identical absolute path on both host and
|
||||
# container — see manager/.env.example's STACKS_DIR comment. This
|
||||
# is how the manager writes new business stacks and shells `docker
|
||||
# compose` against them via the socket above.
|
||||
- "${STACKS_DIR}:${STACKS_DIR}"
|
||||
networks:
|
||||
- next-chair-edge
|
||||
|
||||
networks:
|
||||
next-chair-edge:
|
||||
name: next-chair-edge
|
||||
external: true
|
||||
@@ -0,0 +1,55 @@
|
||||
name: next-chair-marios
|
||||
|
||||
# One business's app container on the shared multi-business VPS setup —
|
||||
# see stacks/infra/compose.yaml for the shared MySQL/Caddy/Dockge this
|
||||
# attaches to, and that file's header for the exact one-time setup steps
|
||||
# (create this business's database, copy .env.business.example, add a
|
||||
# Caddy site file) needed before this file can start.
|
||||
#
|
||||
# docker compose -f stacks/marios/compose.yaml --env-file stacks/marios/.env up -d
|
||||
#
|
||||
# `stacks/blake-heritage/compose.yaml` is the identical shape for the
|
||||
# first business — this is also the shape the management UI (manager/)
|
||||
# generates for every new business it provisions.
|
||||
#
|
||||
# Uses the shared prebuilt image (see the repo root's README/Dockerfile —
|
||||
# `docker build -t nextchair-app:latest .` once per code update) rather
|
||||
# than building from source per business, so adding or updating a
|
||||
# business is just starting/recreating a container, not a rebuild.
|
||||
|
||||
services:
|
||||
next-chair-marios:
|
||||
image: nextchair-app:latest
|
||||
container_name: next-chair-marios
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- next-chair-media-marios:/app/storage/media
|
||||
healthcheck:
|
||||
# No curl/wget in the node:22-alpine base image — a plain Node
|
||||
# request avoids adding a package just for this. See
|
||||
# src/app/api/health/route.ts: healthy as soon as the database is
|
||||
# reachable, even before installation completes.
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"require('http').get('http://localhost:3000/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
networks:
|
||||
- next-chair-edge
|
||||
|
||||
volumes:
|
||||
next-chair-media-marios:
|
||||
name: next-chair-media-marios
|
||||
|
||||
networks:
|
||||
next-chair-edge:
|
||||
name: next-chair-edge
|
||||
external: true
|
||||
Reference in new issue
Block a user