Management UI
CI / ci (push) Canceled after 0s

Attempt 2 of a management ui.
This commit is contained in:
Stefan Lazic committed 2026-09-05 13:39:55 +01:00
1 parent 9fe5b4f038
commit a0d39026c0
54 files changed
+8804 -267

No files matched your search

+136
View File
@@ -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