Custom mysql installation in Docker image with Maven

Although there is an official mysql docker image available on dockerhub, I want to show how to build an image with custom mysql installation and initial configuration resp. database and user creation.

overview

project

First of all the pom.xml where the dockerfile template is filered with some properties, the build artifacts are assembled and pushed into the build process of the docker image and the helper plugin attaches the processed dockerfile as additional artifact.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>eu.christophburmeister.playground</groupId>
	<artifactId>persistence</artifactId>
	<version>0.1-SNAPSHOT</version>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-antrun-plugin</artifactId>
				<version>1.8</version>
				<executions>
					<execution>
						<id>process-docker-files</id>
						<phase>process-resources</phase>
						<configuration>
							<target>
								<echo message="processing the parametrized docker files." />
								<filter token="archive.zip" value="${project.build.finalName}-database.zip" />
								<copy todir="${project.build.directory}/docker-resources"
									filtering="true">
									<fileset dir="${project.basedir}/src/docker" />
								</copy>
							</target>
						</configuration>
						<goals>
							<goal>run</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<artifactId>maven-assembly-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<descriptors>
						<descriptor>src/assembly/sql-assembly.xml</descriptor>
					</descriptors>
				</configuration>
				<executions>
					<execution>
						<id>make-assembly</id>
						<phase>package</phase>
						<goals>
							<goal>single</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>com.spotify</groupId>
				<artifactId>docker-maven-plugin</artifactId>
				<version>0.4.2</version>
				<configuration>
					<imageName>${project.artifactId}</imageName>
					<dockerDirectory>${project.build.directory}/docker-resources</dockerDirectory>
					<noCache>true</noCache>
					<resources>
						<resource>
							<targetPath>/</targetPath>
							<directory>${project.build.directory}</directory>
							<include>${project.build.finalName}-database.zip</include>
						</resource>
					</resources>
				</configuration>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>build</goal>
						</goals>
					</execution>
				</executions>
			</plugin>

			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>build-helper-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>attach-docker-artifacts</id>
						<phase>package</phase>
						<goals>
							<goal>attach-artifact</goal>
						</goals>
						<configuration>
							<artifacts>
								<artifact>
									<file>${project.build.directory}/docker-resources/Dockerfile</file>
									<type>txt</type>
									<classifier>dockerfile</classifier>
								</artifact>
							</artifacts>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

</project>

The assembly descriptor simply collects all src/main/sql items into a zip artifact:

<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>database</id>
<formats>
    <format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
    <fileSet>
        <directory>${project.basedir}/src/main/sql/</directory>
        <outputDirectory>/</outputDirectory>
    </fileSet>
</fileSets>
</assembly>

The template of the Dockerfile contains some properties that will be replaced within maven process. It is based on a standard ubuntu 14.04 image. It receives the zipped sql resources (for now only init_db.sh) and installs the applications „unzip“ and „mysql-server-5.5“. The zipped sql resources are getting unzipped and chmodded with execution rights. There is a slight change to the default my.cnf file where the bind adress is set to 0.0.0.0 so it can be accessed from the outer world. Here one could add more changes. This is a nice solution in case on does not want to provide a complete my.cnf. Additionally the existing default databases of every mysql installation are removed as they are not needed. I commented the line where the my.cnf is viewed with cat. This can be very helpful when analysing image creation problems.
After that some environment variables are defined. Here’s the place where additional properties could be replaced by Maven, but this is up to the desired process. For now they have static values. The rest is straight foward and in the end the /tmp/init_db.sh script is called where the mysqld is started after making some additional configurations (see below).

# Set the base image to mysql
FROM ubuntu:14.04

# Set the maintainer
MAINTAINER Christoph Burmeister

# Add the zipped sql resources
ADD /@archive.zip@ /

# add required applications
RUN apt-get update
RUN apt-get -fy install unzip
RUN apt-get -fy install mysql-server-5.5

# Resouces
# unzip the archive
RUN unzip /@archive.zip@ -d /tmp
RUN chmod 755 /tmp/init_db.sh

# MYSQL
# Remove pre-installed database
RUN rm -rf /var/lib/mysql/*
# make mysql reachable not only from localhost
RUN sed -i 's/^bind-address.*/bind-address=0.0.0.0/' /etc/mysql/my.cnf

#RUN cat /etc/mysql/my.cnf

#define access data
ENV DB_USER springboot
ENV DB_PASSWORD 12345
ENV DB_NAME persistence
ENV VOLUME_HOME "/var/lib/mysql"

# open to mount
VOLUME ["/var/lib/mysql", "/var/log/mysql"]

# Expose the mysql server port
EXPOSE 3306

# start container
CMD ["/bin/bash", "/tmp/init_db.sh"]

The init_db.sh is heavily inspired by https://entwickler.de/online/docker-am-praktischen-beispiel-mit-wordpress-150107.html, so I will not get too deep into details. Basically here are the initial configuration for mysql made. Furthermore in this script the setup of the db and some user configuration is made. In the end the mysqld is started in safe mode. That’s it 🙂

if [[ ! -d $VOLUME_HOME/mysql ]]; then
    echo "=> An empty or uninitialized MySQL volume is detected in $VOLUME_HOME"
    echo "=> Installing MySQL ..."
    if [ ! -f /usr/share/mysql/my-default.cnf ] ; then
        cp /etc/mysql/my.cnf /usr/share/mysql/my-default.cnf
    fi
    mysql_install_db > /dev/null 2>&1
    echo "=> Done!"
    echo "=> Creating admin user ..."
   
    /usr/bin/mysqld_safe > /dev/null 2>&1 &
    RET=1
    while [[ RET -ne 0 ]]; do
       echo "=> Waiting for confirmation of MySQL service startup"
       sleep 5
       mysql -uroot -e "status" > /dev/null 2>&1
       RET=$?
    done

    mysql -uroot -e "CREATE DATABASE ${DB_NAME}"
    mysql -uroot -e "CREATE USER '${DB_USER}'@'%' IDENTIFIED BY '${DB_PASSWORD}'"
    mysql -uroot -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'%' WITH GRANT OPTION"

    mysqladmin -uroot shutdown

    echo "=> Done!"
else
    echo "=> Using an existing volume of MySQL"
fi

/usr/bin/mysqld_safe

After the image is created with „mvn clean install“, it can be obtained from local registry.
Either one use „persistence:latest“ when starting the container or one has to determine the exact image id with command „docker images“.

docker run -d -v /data/mysql:/var/lib/mysql -v /data/log:/var/log/mysql -p 13306:3306 persistence:latest

What das this mean?

  • run -> ok that is simply the start command
  • -d -> detached mode, the container is created and started and then detached from the console
  • -v /data/mysql:/var/lib/mysql -> volume mapping for mysql data. The directory /var/lib/mysql inside the container is mapped to /data/mysql on the host. (/path/on/host:/path/in/container)
  • -v /data/log:/var/log/mysql -> volume mapping for mysql logs. The directory /var/log/mysql inside the container is mapped to /data/log on the host. (/path/on/host:/path/in/container)
  • -p -> port mapping. This means, that port 3306 inside the container is mapped to 13306 outside the container. If there is no ip restriction, docker assumes 0.0.0.0, so it is reachable from the outside world.
  • persistence:latest -> your latest image of persistence

After the container is started with the custom mysql image one can connect with the host’s mysql-client to the mysql-server running in docker container (identified by the 13306 port and the dockerized credentials):

christoph@apollo:~/jtools/eclipse-workspace/persistence$ mysql --host=127.0.0.1 --port=13306 --user=springboot --password=12345 -e "SHOW DATABASES;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| persistence        |
+--------------------+

works 🙂