DB-유

Persistent Data in Synology Docker Containers

· synology, docker, storage

I deleted a container on the Synology Docker package once and watched its database go with it. You only make that mistake one time. Anything written inside a container's own filesystem lives exactly as long as the container, so the fix is to mount folders from the NAS volume into it. Then the data survives updates, recreations and even a move to another NAS. Below is how I do it in the GUI and from the command line on DSM 6.1.

You need the Docker package (plus-series or another x86 model), and SSH enabled under Control Panel → Terminal & SNMP for the CLI part. The package creates /volume1/docker on install, and I keep everything under it.

Step 1: Find out what the container writes

Every image documents its data locations, usually as VOLUME lines in the Dockerfile or in the image's page on Docker Hub. For a few common images:

mysql / mariadb   /var/lib/mysql
postgres          /var/lib/postgresql/data
nextcloud         /var/www/html
gitea             /data
nginx             /etc/nginx/conf.d  and  /usr/share/nginx/html

If you do not know, inspect an already-running container:

sudo docker inspect --format '{{json .Config.Volumes}}' mycontainer

Any path listed there that you did not bind-mount is an anonymous volume, sitting under /volume1/@docker/volumes/<hash>/_data, which is easy to lose and hard to back up.

Step 2: Create the folder structure on the NAS

I give each application its own subfolder under the docker share, and inside that one folder per mount point:

sudo mkdir -p /volume1/docker/mariadb/data
sudo mkdir -p /volume1/docker/mariadb/conf
sudo mkdir -p /volume1/docker/nextcloud/html

Ownership matters. Containers often run as a specific UID (MariaDB uses 999, the LinuxServer.io images take PUID/PGID environment variables). If the process cannot write to the folder, it will fail at startup. When in doubt, check the UID the image uses:

sudo docker run --rm mariadb:10.1 id mysql

and then chown 999:999 /volume1/docker/mariadb/data.

Step 3: Add the mounts in the Docker GUI

Docker → Image → select mariadb → Launch. Give it a name, click Advanced Settings and open the Volume tab. Click Add Folder, browse to docker/mariadb/data and set the Mount path to /var/lib/mysql. Add a second entry for docker/mariadb/conf → /etc/mysql/conf.d, ticking Read-Only for the configuration folder.

Also tick "Enable auto-restart" on the General tab so the container returns after a DSM reboot. Environment variables such as MYSQL_ROOT_PASSWORD go on the Environment tab.

Step 4: Do the same from the CLI

The GUI is fine for one-off containers. For anything I might have to rebuild at 11 p.m., I use docker run or a compose file over SSH, because a text file remembers settings better than I do:

sudo docker run -d --name mariadb \
  --restart unless-stopped \
  -e MYSQL_ROOT_PASSWORD=change-me \
  -v /volume1/docker/mariadb/data:/var/lib/mysql \
  -v /volume1/docker/mariadb/conf:/etc/mysql/conf.d:ro \
  -p 3306:3306 \
  mariadb:10.1

The equivalent docker-compose.yml in /volume1/docker/mariadb/:

version: '2'
services:
  db:
    image: mariadb:10.1
    container_name: mariadb
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: change-me
    volumes:
      - /volume1/docker/mariadb/data:/var/lib/mysql
      - /volume1/docker/mariadb/conf:/etc/mysql/conf.d:ro
    ports:
      - "3306:3306"

Run it with sudo docker-compose up -d. The Docker package ships docker-compose in /usr/local/bin, so it is already on the path.

Containers created from the CLI show up in the Docker GUI as well, although the GUI will refuse to edit some options it did not create. That is fine; I manage those only through compose.

Step 5: Back up the data folders

Because the data now lives in a normal shared folder, Hyper Backup can back it up like anything else. Databases should be stopped or dumped first for consistency. A Task Scheduler entry running as root before the backup window works:

docker exec mariadb sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases --single-transaction' \
  > /volume1/docker/mariadb/dump/all-$(date +%F).sql

Prove it before you trust it

Write something, destroy the container, recreate it and check the data is still there:

sudo docker exec mariadb mysql -uroot -pchange-me -e "CREATE DATABASE persisttest;"
sudo docker rm -f mariadb
sudo docker-compose up -d
sudo docker exec mariadb mysql -uroot -pchange-me -e "SHOW DATABASES LIKE 'persist%';"

The last command should list persisttest. Also confirm from File Station that docker/mariadb/data contains ibdata1 and a directory per database.

Errors I keep running into

  • The container exits immediately and docker logs shows "mysqld: Can't create/write to file '/var/lib/mysql/aria_log_control' (Errcode: 13 "Permission denied")": fix ownership on the host folder as in Step 2.
  • "Error response from daemon: Bind mount failed: '/volume1/docker/mariadb/data' does not exist": folders must exist before docker run; Docker on DSM does not create them for you.
  • Data written to a folder you mounted read-only fails with "Read-only file system": you ticked Read-Only on the wrong entry in the Volume tab.
  • After docker-compose up a second time the GUI shows two containers: you changed container_name or ran compose from a different directory, so compose created a new project. Remove the stray one with docker rm.

Once every container keeps its state under /volume1/docker/<app>, upgrading is just a pull and a recreate.