Deployment Redesign
CI / ci (push) Canceled after 0s

Implemented the deployment redesign.
- Added Dockge-first-class stacks:
  - [stacks/infra/compose.yaml]
  - [stacks/caddy/compose.yaml]
- Added reusable business template: [deploy/business-compose.yaml.template]
- Added automated provisioning: [scripts/create-business.sh]
- Added per-business database/user, secrets, media volume, private networks, and Caddy route generation.
- Removed Traefik and manual business Compose files.
- Updated environment templates, .gitignore, and deployment documentation in [README.md]
Validation completed:
- Docker Compose config passed for infra, Caddy, and generated business stacks.
- Shell syntax validation passed.
- Registry image tags containing / were tested.
- No remaining Traefik or obsolete deployment references found.
This commit is contained in:
Stefan Lazic committed 2026-09-04 20:32:06 +01:00
1 parent 5472db320e
commit 764d087eac
13 files changed
+289 -380

No files matched your search

-103
View File
@@ -1,103 +0,0 @@
name: next-chair-infra
# Shared infrastructure for a multi-business VPS deployment: one Traefik
# reverse proxy (routes each business's domain to its own app container,
# handles TLS via Let's Encrypt) and one MySQL server shared by every
# business — a separate database + user per business, NOT one shared
# schema (see step 1 below).
#
# Deliberately separate from docker-compose.local.yaml, which is the
# single-business local/Docker-Desktop setup and stays untouched by this.
#
# ── One-time setup ───────────────────────────────────────────────────────
# docker network create next-chair-edge
# cp .env.infra.example .env.infra (then fill in real values)
# docker compose -f docker-compose.infra.yaml --env-file .env.infra up -d
#
# ── Per business, after infra is running (see docker-compose.<business>
# .yaml — this repo ships one per business, e.g. docker-compose.blake-
# heritage.yaml) ────────────────────────────────────────────────────────
# 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 .env.business.<slug>, fill it in
# with the credentials just created and that business's domain.
# 3. docker compose -f docker-compose.<business>.yaml --env-file .env.business.<slug> up -d --build
services:
traefik:
image: traefik:v3.2
container_name: next-chair-traefik
restart: unless-stopped
command:
- "--providers.docker=true"
# Only containers with an explicit traefik.enable=true label are
# ever routed to — nothing becomes reachable by accident just for
# sitting on the shared network.
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# Plain HTTP always redirects to HTTPS — no business is ever served
# unencrypted, even before you've thought about it per-business.
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
# Read-only — Traefik only needs to observe container labels/state
# to discover routes, never control the daemon.
- /var/run/docker.sock:/var/run/docker.sock:ro
- next-chair-traefik-certs:/letsencrypt
networks:
- next-chair-edge
next-chair-mysql:
image: mysql:8.0
container_name: next-chair-mysql
restart: unless-stopped
env_file:
- .env.infra
# 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.infra) 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
volumes:
next-chair-mysql-data:
name: next-chair-mysql-data
next-chair-traefik-certs:
name: next-chair-traefik-certs
networks:
next-chair-edge:
name: next-chair-edge
external: true