Upgrading MariaDB on Synology Docker: 10.11 to 12.3, WordPress Fix and Nightly Backups

A real MariaDB 10.11 to 12.3 upgrade on Synology, fixing WordPress's broken Docker network and scheduling verified nightly SQL backups before WUD updates.

I wanted to bring the MariaDB container behind Realm Labs up to a current long-term-support release, then keep it on that release series for routine updates. The upgrade from MariaDB 10.11 to 12.3.3 itself completed successfully. The unexpected part was WordPress going offline with “Error establishing a database connection” — not because the database was corrupt, but because its Docker network connection had disappeared during the Portainer redeployment.

This is the actual recovery and backup procedure on my Synology NAS, with all secrets removed. If you follow it, adapt volume paths, database names, credentials and stack names to your setup.

Before the upgrade: confirm where the database lives

My MariaDB database files were in a bind mount outside the container:

docker inspect mariadb --format '{{range .Mounts}}{{println .Source " -> " .Destination}}{{end}}'

The output identified /volume1/Sektor/nfs/docker-volumes/mariadb mapped to /var/lib/mysql. The database folder was approximately 329 MB, with ample free space on the NAS. The first priority was an offline copy of those files, with the old image kept available for rollback.

Take a consistent pre-upgrade backup

I stopped MariaDB briefly to capture a filesystem-level archive, then restarted it. A live tar of InnoDB files would not give the same consistency guarantee.

sudo mkdir -p /volume1/Sektor/nfs/backups/mariadb
docker stop mariadb
sudo tar -C /volume1/Sektor/nfs/docker-volumes \
  -czpf "/volume1/Sektor/nfs/backups/mariadb/mariadb-$(date +%Y%m%d-%H%M%S).tar.gz" \
  mariadb
docker start mariadb

Check each command for errors. If the archive operation fails, restart the database and resolve the backup problem before attempting an upgrade. My archive was 22 MB after compression. Test archive integrity, and ideally carry out a restore rehearsal on a separate instance:

sudo tar -tzf /path/to/mariadb-preupgrade.tar.gz >/dev/null && echo "Archive readable"

Upgrade the image, not the data path

For the upgrade I kept the same bind mount and pinned the image to the MariaDB 12.3 series, rather than latest. The important environment flag was MARIADB_AUTO_UPGRADE=1, which allows the official container entrypoint to run system-table upgrades when needed. An already-initialised MariaDB data directory retains credentials stored inside the database; changing an initial password environment variable does not reset those accounts.

services:
  mariadb:
    image: mariadb:12.3
    container_name: mariadb
    restart: unless-stopped
    environment:
      - TZ=Europe/London
      - MARIADB_AUTO_UPGRADE=1
    volumes:
      - /volume1/Sektor/nfs/docker-volumes/mariadb:/var/lib/mysql
    ports:
      - "3306:3306"
    networks:
      - default
      - web
  phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    container_name: phpmyadmin
    restart: unless-stopped
    environment:
      - PMA_HOST=mariadb
      - PMA_PORT=3306
      - UPLOAD_LIMIT=256M
      - TZ=Europe/London
    ports:
      - "8081:80"
    depends_on:
      - mariadb
networks:
  web:
    external: true

The web network is an existing external Docker network shared with WordPress. Yours may be named differently. The MariaDB service also stays on the stack’s default network so phpMyAdmin can resolve it. Notice there are no actual passwords in this published Compose sample.

What the MariaDB upgrade logs told us

After deploying, docker exec mariadb mariadb --version reported 12.3.3-MariaDB. The logs showed Finished mariadb-upgrade, followed by a clean startup and ready for connections on port 3306. There was an io_uring warning with a fallback to libaio; in this environment that was not the reason for the WordPress failure.

A quick mariadb-admin ping returned access denied because it tried root without a password. That result did not demonstrate a broken database. For an authenticated check, use an interactive prompt instead of writing secrets on the command line:

docker exec -it mariadb mariadb -u root -p -e "SELECT VERSION(); SHOW DATABASES;"

WordPress down: the Docker networking fix

Immediately after the MariaDB redeployment, Realm Labs returned HTTP 500 and the WordPress database-connection error. Inspecting Docker networks gave us the decisive clue: wordpress was on web, but mariadb was only on mariadb_default.

docker ps --format 'table {{.Names}}\t{{.Networks}}'
docker exec wordpress printenv WORDPRESS_DB_HOST

WordPress was configured to use mariadb:3306, so the hostname could no longer resolve between the disconnected Docker networks. For an immediate recovery, I attached MariaDB to WordPress’s network:

docker network connect web mariadb
docker exec wordpress getent hosts mariadb

The site returned immediately. But docker network connect alone is not a permanent Compose definition: a subsequent Portainer redeployment can recreate the container without that manually added network. The permanent fix was to declare both default and external web networks in the MariaDB stack, as shown above. I redeployed and verified both network memberships.

Nightly SQL backups before container updates

The pre-upgrade tar archive is a rollback snapshot, not a substitute for current nightly backups. I set up a logical mariadb-dump job at 01:30 ahead of WUD’s 02:00 check. The root password lives in a root-owned client options file (.backup.cnf, mode 600) rather than inside a shell script. Create that file locally without publishing the password:

[client]
user=root
password="YOUR_REAL_PASSWORD"

Below is an adapted backup script. It uses a temporary MariaDB client container attached to the running server’s network namespace, writes the dump to a temporary file, verifies the gzip archive, then keeps backups for approximately seven days. It intentionally leaves the older pre-upgrade tar archive alone.

#!/bin/sh
set -eu
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
DIR=/volume1/Sektor/nfs/backups/mariadb
STAMP=$(date +%Y%m%d-%H%M%S)
TMP="$DIR/.backup-$STAMP.sql"
OUT="$DIR/seido-mariadb-daily-$STAMP.sql.gz"
umask 077
trap 'rm -f "$TMP" "$TMP.gz"' EXIT HUP INT TERM
docker run --rm --network container:mariadb \
  -v "$DIR/.backup.cnf:/run/backup.cnf:ro" \
  mariadb:12.3 mariadb-dump \
  --defaults-extra-file=/run/backup.cnf \
  --host=127.0.0.1 --all-databases \
  --single-transaction --quick \
  --routines --events --triggers --hex-blob > "$TMP"
test -s "$TMP"
gzip "$TMP"
gzip -t "$TMP.gz"
mv "$TMP.gz" "$OUT"
chmod 600 "$OUT"
find "$DIR" -maxdepth 1 -type f \
  -name 'seido-mariadb-daily-*.sql.gz' -mtime +6 -delete
echo "Backup completed: $OUT"

I saved the script at /usr/local/bin/seido-mariadb-backup.sh, made it executable only by root, and created a Synology DSM Task Scheduler user-defined task running as root at 01:30:

/bin/sh /usr/local/bin/seido-mariadb-backup.sh

For the real-world test, the logical backup took around 11 seconds and produced a 6.8 MB gzip file. Running the job directly from DSM created a second archive, and gzip -t verified it. The scheduled 01:30 execution still needs to be observed on its first night. A successful gzip integrity test is not the same as a full restoration test.

Permissions, retention and rollback

Synology ACLs deserve attention. After the scheduled test, the backup directory showed permissive Unix mode bits and an ACL marker, so I recommend checking both DSM ACLs and Unix permissions. SQL dumps may contain user data and WordPress credential material. Keep backup files and credentials root-readable only, and send another copy off-NAS using Hyper Backup or an equivalent tool.

My nightly cleanup retains about seven days based on file age; the historical 22 MB tar is excluded by its filename. If a future database upgrade fails, preserve the modified data directory before trying anything else. Downgrading a MariaDB image alone may not be sufficient after on-disk upgrades. Restore the matching pre-upgrade database files and matching old image, or restore a logical dump to a compatible fresh instance.

Would I do it again?

Yes, with one clear improvement: verify Docker network memberships in the Compose stack before upgrading. MariaDB 12.3.3 itself came up cleanly; the avoidable outage was a missing external network definition. With that fixed, the site came back, phpMyAdmin continued to work, and the database now has a consistent nightly backup routine before container maintenance.

For the update side of this setup, see the companion guide on automating Docker updates with WUD on Synology. Official background: MariaDB Docker environment variables and mariadb-dump documentation.