VPS
Example of vps deploy with infra split from business. Keeps one mysql instance for all.
This commit is contained in:
6 files changed
+359
No files matched your search
@@ -0,0 +1,103 @@
|
||||
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
|
||||
Reference in new issue
Block a user