FROM debian:stretch-slim
# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r mysql &&useradd -r -g mysql mysql
RUN apt-get update &&apt-getinstall -y --no-install-recommends gnupg dirmngr &&rm -rf /var/lib/apt/lists/*
# add gosu for easy step-down from root
ENV GOSU_VERSION 1.7
RUN set -x \
&&apt-get update &&apt-getinstall -y --no-install-recommends ca-certificates wget&&rm -rf /var/lib/apt/lists/* \
&&wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" \
&&wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" \
&&export GNUPGHOME="$(mktemp -d)" \
&& gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
&& gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
&& gpgconf --kill all \
&&rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc \
&&chmod +x /usr/local/bin/gosu \
&& gosu nobody true \
&&apt-get purge -y --auto-remove ca-certificates wget
RUN mkdir /docker-entrypoint-initdb.d
RUN apt-get update &&apt-getinstall -y --no-install-recommends \
# for MYSQL_RANDOM_ROOT_PASSWORD
pwgen \
# for mysql_ssl_rsa_setup
openssl \
# FATAL ERROR: please install the following Perl modules before executing /usr/local/mysql/scripts/mysql_install_db:# File::Basename# File::Copy# Sys::Hostname# Data::Dumper
perl \
&&rm -rf /var/lib/apt/lists/*
RUN set -ex; \
# gpg: key 5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported
key='A4A9406876FCBD3C456770C88C718D3B5072E1F5'; \
export GNUPGHOME="$(mktemp -d)"; \
gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
gpg --batch --export "$key"> /etc/apt/trusted.gpg.d/mysql.gpg; \
gpgconf --kill all; \
rm -rf "$GNUPGHOME"; \
apt-key list > /dev/null
ENV MYSQL_MAJOR 5.7
ENV MYSQL_VERSION 5.7.29-1debian9
RUN echo"deb http://repo.mysql.com/apt/debian/ stretch mysql-${MYSQL_MAJOR}"> /etc/apt/sources.list.d/mysql.list
# the "/var/lib/mysql" stuff here is because the mysql-server postinst doesn't have an explicit way to disable the mysql_install_db codepath besides having a database already "configured" (ie, stuff in /var/lib/mysql/mysql)# also, we set debconf keys to make APT a little quieter
RUN { \
echo mysql-community-server mysql-community-server/data-dir select''; \
echo mysql-community-server mysql-community-server/root-pass password ''; \
echo mysql-community-server mysql-community-server/re-root-pass password ''; \
echo mysql-community-server mysql-community-server/remove-test-db selectfalse; \
}| debconf-set-selections \
&&apt-get update &&apt-getinstall -y mysql-server="${MYSQL_VERSION}"&&rm -rf /var/lib/apt/lists/* \
&&rm -rf /var/lib/mysql &&mkdir -p /var/lib/mysql /var/run/mysqld \
&&chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \
# ensure that /var/run/mysqld (used for socket and lock files) is writable regardless of the UID our mysqld instance ends up having at runtime&&chmod 777 /var/run/mysqld \
# comment out a few problematic configuration values&&find /etc/mysql/ -name '*.cnf' -print0 \
|xargs -0 grep -lZE '^(bind-address|log)' \
|xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/' \
# don't reverse lookup hostnames, they are usually another container&&echo'[mysqld]\nskip-host-cache\nskip-name-resolve'> /etc/mysql/conf.d/docker.cnf
VOLUME /var/lib/mysql
COPY docker-entrypoint.sh /usr/local/bin/
RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 3306 33060
CMD ["mysqld"]
docker-entrypoint.sh
#!/bin/bashset -eo pipefail
shopt -s nullglob
# logging functions
mysql_log(){
local type="$1";shiftprintf'%s [%s] [Entrypoint]: %s\n'"$(date --rfc-3339=seconds)""$type""$*"}
mysql_note(){
mysql_log Note "$@"}
mysql_warn(){
mysql_log Warn "$@">&2
}
mysql_error(){
mysql_log ERROR "$@">&2
exit 1
}# usage: file_env VAR [DEFAULT]# ie: file_env 'XYZ_DB_PASSWORD' 'example'# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
file_env(){
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"if["${!var:-}"]&&["${!fileVar:-}"];then
mysql_error "Both $var and $fileVar are set (but are exclusive)"fi
local val="$def"if["${!var:-}"];then
val="${!var}"elif["${!fileVar:-}"];then
val="$(<"${!fileVar}")"fiexport"$var"="$val"
unset "$fileVar"}# check to see if this file is being run or sourced from another script
_is_sourced(){# https://unix.stackexchange.com/a/215279["${#FUNCNAME[@]}" -ge 2 ] \
&&["${FUNCNAME[0]}"='_is_sourced'] \
&&["${FUNCNAME[1]}"='source']}# usage: docker_process_init_files [file [file [...]]]# ie: docker_process_init_files /always-initdb.d/*# process initializer files, based on file extensions
docker_process_init_files(){# mysql here for backwards compatibility "${mysql[@]}"
mysql=( docker_process_sql )echo
local f
for f;docase"$f"in
*.sh) mysql_note "$0: running $f";."$f";;
*.sql) mysql_note "$0: running $f"; docker_process_sql <"$f";echo;;
*.sql.gz) mysql_note "$0: running $f"; gunzip -c "$f"| docker_process_sql;echo;;
*) mysql_warn "$0: ignoring $f";;
esac
echodone}
mysql_check_config(){
local toRun=("$@" --verbose --help ) errors
if! errors="$("${toRun[@]}" 2>&1 >/dev/null)";then
mysql_error $'mysqld failed while attempting to check config\n\tcommand was: '"${toRun[*]}"$'\n\t'"$errors"fi}# Fetch value from server config# We use mysqld --verbose --help instead of my_print_defaults because the# latter only show values present in config files, and not server defaults
mysql_get_config(){
local conf="$1";shift"$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null \
|awk -v conf="$conf"'$1 == conf && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }'# match "datadir /some/path with/spaces in/it here" but not "--xyz=abc\n datadir (xyz)"}# Do a temporary startup of the MySQL server, for init purposes
docker_temp_server_start(){if["${MYSQL_MAJOR}"='5.6']||["${MYSQL_MAJOR}"='5.7'];then"$@" --skip-networking --socket="${SOCKET}"&
mysql_note "Waiting for server startup"
local i
for i in{30..0};do# only use the root password if the database has already been initializaed# so that it won't try to fill in a password file when it hasn't been set yet
extraArgs=()if[ -z "$DATABASE_ALREADY_EXISTS"];then
extraArgs+=('--dont-use-mysql-root-password')fiif docker_process_sql "${extraArgs[@]}" --database=mysql <<<'SELECT 1'&> /dev/null;thenbreakfisleep 1
doneif["$i"= 0 ];then
mysql_error "Unable to start server."fielse# For 5.7+ the server is ready for use as soon as startup command unblocksif!"$@" --daemonize --skip-networking --socket="${SOCKET}";then
mysql_error "Unable to start server."fifi}# Stop the server. When using a local socket file mysqladmin will block until# the shutdown is complete.
docker_temp_server_stop(){if! mysqladmin --defaults-extra-file=<( _mysql_passfile )shutdown -uroot --socket="${SOCKET}";then
mysql_error "Unable to shut down server."fi}# Verify that the minimally required password settings are set for new databases.
docker_verify_minimum_env(){if[ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD"];then
mysql_error $'Database is uninitialized and password option is not specified\n\tYou need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD'fi}# creates folders for the database# also ensures permission for user mysql of run as root
docker_create_db_directories(){
local user; user="$(id -u)"# TODO other directories that are used by default? like /var/lib/mysql-files# see https://github.com/docker-library/mysql/issues/562mkdir -p "$DATADIR"if["$user"="0"];then# this will cause less disk access than `chown -R`find"$DATADIR" \! -user mysql -exec chown mysql '{}' +
fi}# initializes the database directory
docker_init_database_dir(){
mysql_note "Initializing database files"if["$MYSQL_MAJOR"='5.6'];then
mysql_install_db --datadir="$DATADIR" --rpm --keep-my-cnf "${@:2}"else"$@" --initialize-insecure
fi
mysql_note "Database files initialized"ifcommand -v mysql_ssl_rsa_setup > /dev/null &&[! -e "$DATADIR/server-key.pem"];then# https://github.com/mysql/mysql-server/blob/23032807537d8dd8ee4ec1c4d40f0633cd4e12f9/packaging/deb-in/extra/mysql-systemd-start#L81-L84
mysql_note "Initializing certificates"
mysql_ssl_rsa_setup --datadir="$DATADIR"
mysql_note "Certificates initialized"fi}# Loads various settings that are used elsewhere in the script# This should be called after mysql_check_config, but before any other functions
docker_setup_env(){# Get configdeclare -g DATADIR SOCKET
DATADIR="$(mysql_get_config 'datadir'"$@")"
SOCKET="$(mysql_get_config 'socket'"$@")"# Initialize values that might be stored in a file
file_env 'MYSQL_ROOT_HOST''%'
file_env 'MYSQL_DATABASE'
file_env 'MYSQL_USER'
file_env 'MYSQL_PASSWORD'
file_env 'MYSQL_ROOT_PASSWORD'declare -g DATABASE_ALREADY_EXISTS
if[ -d "$DATADIR/mysql"];then
DATABASE_ALREADY_EXISTS='true'fi}# Execute sql script, passed via stdin# usage: docker_process_sql [--dont-use-mysql-root-password] [mysql-cli-args]# ie: docker_process_sql --database=mydb <<<'INSERT ...'# ie: docker_process_sql --dont-use-mysql-root-password --database=mydb <my-file.sql
docker_process_sql(){
passfileArgs=()if['--dont-use-mysql-root-password'="$1"];then
passfileArgs+=("$1")shiftfi# args sent in can override this db, since they will be later in the commandif[ -n "$MYSQL_DATABASE"];thenset -- --database="$MYSQL_DATABASE""$@"fi
mysql --defaults-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}""$@"}# Initializes database with timezone info and root password, plus optional extra db/user
docker_setup_db(){# Load timezone info into databaseif[ -z "$MYSQL_INITDB_SKIP_TZINFO"];then# sed is for https://bugs.mysql.com/bug.php?id=20545
mysql_tzinfo_to_sql /usr/share/zoneinfo \
|sed's/Local time zone must be set--see zic manual page/FCTY/' \
| docker_process_sql --dont-use-mysql-root-password --database=mysql
# tell docker_process_sql to not use MYSQL_ROOT_PASSWORD since it is not set yetfi# Generate random root passwordif[ -n "$MYSQL_RANDOM_ROOT_PASSWORD"];thenexport MYSQL_ROOT_PASSWORD="$(pwgen -1 32)"
mysql_note "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"fi# Sets root password and creates root users for non-localhost hosts
local rootCreate=# default root to listen for connections from anywhereif[ -n "$MYSQL_ROOT_HOST"]&&["$MYSQL_ROOT_HOST"!='localhost'];then# no, we don't care if read finds a terminating character in this heredoc# https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151read -r -d '' rootCreate <<-EOSQL ||true
CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';
GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ;
EOSQL
fi
local passwordSet=if["$MYSQL_MAJOR"='5.6'];then# no, we don't care if read finds a terminating character in this heredoc (see above)read -r -d '' passwordSet <<-EOSQL ||true
DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost');
SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}');
-- 5.5: https://github.com/mysql/mysql-server/blob/e48d775c6f066add457fa8cfb2ebc4d5ff0c7613/scripts/mysql_secure_installation.sh#L192-L210
-- 5.6: https://github.com/mysql/mysql-server/blob/06bc670db0c0e45b3ea11409382a5c315961f682/scripts/mysql_secure_installation.sh#L218-L236
-- 5.7: https://github.com/mysql/mysql-server/blob/913071c0b16cc03e703308250d795bc381627e37/client/mysql_secure_installation.cc#L792-L818
-- 8.0: https://github.com/mysql/mysql-server/blob/b93c1661d689c8b7decc7563ba15f6ed140a4eb6/client/mysql_secure_installation.cc#L726-L749
DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%';
-- https://github.com/docker-library/mysql/pull/479#issuecomment-414561272 ("This is only needed for 5.5 and 5.6")
EOSQL
else# no, we don't care if read finds a terminating character in this heredoc (see above)read -r -d '' passwordSet <<-EOSQL ||true
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';
EOSQL
fi# tell docker_process_sql to not use MYSQL_ROOT_PASSWORD since it is just now being set
docker_process_sql --dont-use-mysql-root-password --database=mysql <<-EOSQL
-- What's done in this file shouldn't be replicated
-- or products like mysql-fabric won't work
SET @@SESSION.SQL_LOG_BIN=0;
${passwordSet}
GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ;
FLUSH PRIVILEGES ;
${rootCreate}
DROP DATABASE IF EXISTS test ;
EOSQL
# Creates a custom database and user if specified
if [ -n "$MYSQL_DATABASE" ]; then
mysql_note "Creating database ${MYSQL_DATABASE}"
docker_process_sql --database=mysql <<<"CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;"
fi
if [ -n "$MYSQL_USER" ] && [ -n "$MYSQL_PASSWORD" ]; then
mysql_note "Creating user ${MYSQL_USER}"
docker_process_sql --database=mysql <<<"CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;"
if [ -n "$MYSQL_DATABASE" ]; then
mysql_note "Giving user ${MYSQL_USER} access to schema ${MYSQL_DATABASE}"
docker_process_sql --database=mysql <<<"GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;"
fi
docker_process_sql --database=mysql <<<"FLUSH PRIVILEGES ;"
fi
}
_mysql_passfile() {
# echo the password to the "file" the client uses
# the client command will use process substitution to create a file on the fly
# ie: --defaults-file=<( _mysql_passfile )
if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MYSQL_ROOT_PASSWORD" ]; then
cat <<-EOF
[client]
password="${MYSQL_ROOT_PASSWORD}"
EOF
fi
}
# Mark root user as expired so the password must be changed before anything
# else can be done (only supported for 5.6+)
mysql_expire_root_user() {
if [ -n "$MYSQL_ONETIME_PASSWORD" ]; then
docker_process_sql --database=mysql <<-EOSQL
ALTER USER 'root'@'%' PASSWORD EXPIRE;
EOSQL
fi
}
# check arguments for an option that would cause mysqld to stop
# return true if there is one
_mysql_want_help() {
local arg
for arg; do
case "$arg" in
-'?'|--help|--print-defaults|-V|--version)
return 0
;;
esac
done
return 1
}
_main() {
# if command starts with an option, prepend mysqld
if [ "${1:0:1}" = '-' ]; then
set -- mysqld "$@"
fi
# skip setup if they aren't running mysqld or want an option that stops mysqld
if["$1"='mysqld']&&! _mysql_want_help "$@";then
mysql_note "Entrypoint script for MySQL Server ${MYSQL_VERSION} started."
mysql_check_config "$@"# Load various environment variables
docker_setup_env "$@"
docker_create_db_directories
# If container is started as root user, restart as dedicated mysql userif["$(id -u)"="0"];then
mysql_note "Switching to dedicated user 'mysql'"exec gosu mysql "$BASH_SOURCE""$@"fi# there's no database, so it needs to be initializedif[ -z "$DATABASE_ALREADY_EXISTS"];then
docker_verify_minimum_env
docker_init_database_dir "$@"
mysql_note "Starting temporary server"
docker_temp_server_start "$@"
mysql_note "Temporary server started."
docker_setup_db
docker_process_init_files /docker-entrypoint-initdb.d/*
mysql_expire_root_user
mysql_note "Stopping temporary server"
docker_temp_server_stop
mysql_note "Temporary server stopped"echo
mysql_note "MySQL init process done. Ready for start up."echofifiexec"$@"}# If we are sourced from elsewhere, don't perform any further actionsif! _is_sourced;then
_main "$@"fi