deploy/jorgecuadros-db.stack.yml: canonical internal MySQL for the platform, targeting the Portainer local endpoint on cubex (3-node Swarm). Parametrized (MYSQL_PORT / MYSQL_SERVER_ID) so one file deploys both environments as two stacks with Swarm-namespaced volumes: dev -> jorgecuadros-dev-db :3307 server-id 11 prod -> jorgecuadros-prod-db :3306 server-id 1 (replication source) Swarm-correct: named volume (no bind mount), pinned to one node via node.labels.jorgecuadros_db==true (cubex labeled), binlog+GTID enabled from the start so prod can be the VPS replication source without reconfigure. Dev deployed and verified: MySQL 8.4.10 reachable at 192.168.4.212:3307, gtid_mode ON, database jorgecuadros present. Secrets live in gitignored deploy/.env.dev, injected via Portainer stack env at deploy time. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
3.2 KiB
YAML
77 lines
3.2 KiB
YAML
# Canonical internal MySQL for the Jorge Cuadros platform.
|
|
#
|
|
# Target: Portainer "local" endpoint on cubex (192.168.4.212:9443), which is a
|
|
# 3-node Docker Swarm. This is the source-of-truth database from the plan's
|
|
# "internal server" — the migration transform+load and the NestJS API both
|
|
# point at it, and (later) it becomes the replication SOURCE for the VPS.
|
|
#
|
|
# DEV / PROD as two stacks from this one file. Deploy it twice with different
|
|
# stack names + env_data:
|
|
# dev : stack jorgecuadros-dev-db MYSQL_PORT=3307 MYSQL_SERVER_ID=11
|
|
# prod: stack jorgecuadros-prod-db MYSQL_PORT=3306 MYSQL_SERVER_ID=1
|
|
# Swarm namespaces the named volume by stack name, so the two environments get
|
|
# fully isolated data (jorgecuadros-dev-db_mysql_data vs -prod-db_...) with no
|
|
# extra config. Distinct published ports let both run on the same node at once.
|
|
# server-id must be unique per environment (prod=1 is the replication source).
|
|
#
|
|
# Swarm statefulness rules (see portainer-gitea-deploy skill):
|
|
# - named volume, never a relative bind mount (the API deploy path won't
|
|
# create host dirs -> "bind source path does not exist").
|
|
# - a named volume is LOCAL to whichever node the task lands on. With no
|
|
# shared storage, the DB MUST be pinned to one node or a reschedule would
|
|
# start against a fresh empty volume. Pinned here via a node label so it is
|
|
# not tied to a hostname: label exactly one node with
|
|
# docker node update --label-add jorgecuadros_db=true <node>
|
|
# and MySQL will always run there, reusing the same volume.
|
|
#
|
|
# Secrets (MYSQL_ROOT_PASSWORD, MYSQL_PASSWORD) are injected at deploy time via
|
|
# Portainer env_data / stack environment, not committed here.
|
|
|
|
version: "3.8"
|
|
|
|
services:
|
|
mysql:
|
|
image: mysql:8.4
|
|
command:
|
|
# (caching_sha2_password is already the default in 8.4; the old
|
|
# --default-authentication-plugin flag was REMOVED in 8.4 and aborts boot.)
|
|
# binlog + GTID on from day one so this node can be the replication
|
|
# SOURCE for the VPS replica later without a restart/reconfigure.
|
|
- --server-id=${MYSQL_SERVER_ID:-1}
|
|
- --log-bin=mysql-bin
|
|
- --binlog-format=ROW
|
|
- --gtid-mode=ON
|
|
- --enforce-gtid-consistency=ON
|
|
environment:
|
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-jorgecuadros}
|
|
MYSQL_USER: ${MYSQL_USER:-jorgecuadros}
|
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?MYSQL_PASSWORD must be set}
|
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD must be set}
|
|
ports:
|
|
# Swarm ingress publishes on every node, so this is reachable at
|
|
# 192.168.4.212:${MYSQL_PORT} regardless of which node the task pins to.
|
|
- target: 3306
|
|
published: ${MYSQL_PORT:-3306}
|
|
protocol: tcp
|
|
mode: ingress
|
|
volumes:
|
|
- mysql_data:/var/lib/mysql
|
|
deploy:
|
|
replicas: 1
|
|
placement:
|
|
constraints:
|
|
- node.labels.jorgecuadros_db == true
|
|
restart_policy:
|
|
condition: any
|
|
update_config:
|
|
order: stop-first
|
|
healthcheck:
|
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 12
|
|
start_period: 40s
|
|
|
|
volumes:
|
|
mysql_data:
|