# 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 # 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: