based on @ulrich answer, but with mysqldump :
DATE_TIME=`date +"%Y%m%d_%H%M"
CONTAINER_NAME="mysql_server"
CONTAINER=`docker ps -q -f name=${CONTAINER_NAME}`
PASSWORD=`docker exec ${CONTAINER} sh -c 'echo $MYSQL_ROOT_PASSWORD'`
BACKUP_FILE=${DATE_TIME}_${CONTAINER_NAME}.backup.sql.gz
CONTAINER_BACKUP_DIR="/var/lib/mysql/"
HOST_DATA_DIR="/path/on/my/VM/correspond/to/docker/volume/"
HOST_BACKUP_DIR="/other/path/on/VM/"
## run backup in container
docker exec ${CONTAINER} sh -c "/usr/bin/mysqldump -h localhost -u root -p${PASSWORD} --all-databases | gzip -9 -c > ${CONTAINER_BACKUP_DIR}${BACKUP_FILE}"
## move to final folder
mv ${HOST_DATA_DIR}${BACKUP_FILE} ${HOST_BACKUP_DIR}
HOST_DATA_DIR and HOST_BACKUP_DIR are differents because I have an other daemon to save backup to a data center, and because in HOST_DATA_DIR, root can only read and execute (r_x).
For all folder paths, be careful to have a final '/', otherwise you should be surprised!
and if you run it automatically with a CRON job, it should be helpfull to manage rotate to delete old backup:
## rotate save management to keep only last MAX_BACKUP files
MAX_BACKUP=3
${HOST_DATA_DIR}rotate-databases-backup.log
echo ${HOST_BACKUP_DIR}${BACKUP_FILE} >> ${HOST_DATA_DIR}rotate-databases-backup.log
BACKUPED_NB=$(ls ${HOST_BACKUP_DIR}*${CONTAINER_NAME}.backup.sql.gz | wc --lines)
TOTAL=$(wc --lines < ${HOST_DATA_DIR}rotate-databases-backup.log)
FILES_NB_TO_DELETE=$(expr $TOTAL - $MAX_BACKUP)
if [[ $FILES_NB_TO_DELETE -gt 0 ]]; then
head --lines=$FILES_NB_TO_DELETE ${HOST_DATA_DIR}rotate-databases-backup.log | xargs rm
ls ${HOST_BACKUP_DIR}*${CONTAINER_NAME}.backup.sql.gz > ${HOST_DATA_DIR}rotate-databases-backup.log
fi