232

I am trying to create a container with a MySQL database and add a schema to these database.

My current Dockerfile is:

FROM mysql
MAINTAINER  (me) <email>

# Copy the database schema to the /data directory
COPY files/epcis_schema.sql /data/epcis_schema.sql

# Change the working directory
WORKDIR data

CMD mysql -u $MYSQL_USER -p $MYSQL_PASSWORD $MYSQL_DATABASE < epcis_schema.sql

In order to create the container I am following the documentation provided on Docker and executing this command:

docker run --name ${CONTAINER_NAME} -e MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD} -e MYSQL_USER=${DB_USER} -e MYSQL_PASSWORD=${DB_USER_PASSWORD} -e MYSQL_DATABASE=${DB_NAME} -d mvpgomes/epcisdb

But when I execute this command the Container is not created and in the Container status it is possible to see that the CMD was not executed successfully, in fact only the mysql command is executed.

Anyway, is there a way to initialize the database with the schema or do I need to perform these operations manually?

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
Marcus Gomes
  • 2,534
  • 2
  • 13
  • 15
  • i assume that you want to create a new image with a database that is seeded and ready to go? – Greg Mar 19 '15 at 13:29
  • Yes, that is exactly what I want to do. – Marcus Gomes Mar 19 '15 at 13:37
  • See also: [Creating a docker mysql container with a prepared database scheme](https://serverfault.com/questions/796762/creating-a-docker-mysql-container-with-a-prepared-database-scheme) – Martin Thoma Jul 24 '17 at 12:30
  • Explained here too: https://medium.com/@lvthillo/customize-your-mysql-database-in-docker-723ffd59d8fb – lvthillo Apr 13 '18 at 11:31

11 Answers11

171

I had this same issue where I wanted to initialize my MySQL Docker instance's schema, but I ran into difficulty getting this working after doing some Googling and following others' examples. Here's how I solved it.

1) Dump your MySQL schema to a file.

mysqldump -h <your_mysql_host> -u <user_name> -p --no-data <schema_name> > schema.sql

2) Use the ADD command to add your schema file to the /docker-entrypoint-initdb.d directory in the Docker container. The docker-entrypoint.sh file will run any files in this directory ending with ".sql" against the MySQL database.

Dockerfile:

FROM mysql:5.7.15

MAINTAINER me

ENV MYSQL_DATABASE=<schema_name> \
    MYSQL_ROOT_PASSWORD=<password>

ADD schema.sql /docker-entrypoint-initdb.d

EXPOSE 3306

3) Start up the Docker MySQL instance.

docker-compose build
docker-compose up

Thanks to Setting up MySQL and importing dump within Dockerfile for clueing me in on the docker-entrypoint.sh and the fact that it runs both SQL and shell scripts!

Community
  • 1
  • 1
Brett Cooper
  • 1,726
  • 1
  • 9
  • 4
  • 4
    This is the definitely the better answer. Copying DDLs and initialization scripts to the /docker-entrypoint-initdb.d directory is what the dockerhub mysql docs say to do with such things. – chacewells Oct 07 '18 at 02:03
  • 4
    What happens when something changes in `schema.sql`? I want my db to be updated accordingly. Also what happens when I build this container second time? Will db get destroyed and created again? – Goga Koreli Jan 20 '19 at 09:55
  • 2
    @Goga Koreli Docker runs this init command only if there is no data entry in /var/lib/mysql – FishingIsLife Feb 06 '19 at 11:20
  • It works only when DB name is specified in the Dockerfile but will not take any effect when DB creation is in the SQL file – JackTheKnife May 22 '22 at 19:59
  • What if I want to use another user to run initiliaze script? – link89 Oct 16 '22 at 03:59
  • Baking the SQL schema into the image is one of the worst suggestions. Would you build a brand new mysql image for each schema change? Mount the schema.sql into /docker-entrypoint-initdb.d instead. – rubenhak Feb 16 '23 at 20:24
124

I am sorry for this super long answer, but, you have a little way to go to get where you want. I will say that normally you wouldn't put the storage for the database in the same container as the database itself, you would either mount a host volume so that the data persists on the docker host, or, perhaps a container could be used to hold the data (/var/lib/mysql). Also, I am new to mysql, so, this might not be super efficient. That said...

I think there may be a few issues here. The Dockerfile is used to create an image. You need to execute the build step. At a minimum, from the directory that contains the Dockerfile you would do something like :

docker build .

The Dockerfile describes the image to create. I don't know much about mysql (I am a postgres fanboy), but, I did a search around the interwebs for 'how do i initialize a mysql docker container'. First I created a new directory to work in, I called it mdir, then I created a files directory which I deposited a epcis_schema.sql file which creates a database and a single table:

create database test;
use test;

CREATE TABLE testtab
(
id INTEGER AUTO_INCREMENT,
name TEXT,
PRIMARY KEY (id)
) COMMENT='this is my test table';

Then I created a script called init_db in the files directory:

#!/bin/bash

# Initialize MySQL database.
# ADD this file into the container via Dockerfile.
# Assuming you specify a VOLUME ["/var/lib/mysql"] or `-v /var/lib/mysql` on the `docker run` command…
# Once built, do e.g. `docker run your_image /path/to/docker-mysql-initialize.sh`
# Again, make sure MySQL is persisting data outside the container for this to have any effect.

set -e
set -x

mysql_install_db

# Start the MySQL daemon in the background.
/usr/sbin/mysqld &
mysql_pid=$!

until mysqladmin ping >/dev/null 2>&1; do
  echo -n "."; sleep 0.2
done

# Permit root login without password from outside container.
mysql -e "GRANT ALL ON *.* TO root@'%' IDENTIFIED BY '' WITH GRANT OPTION"

# create the default database from the ADDed file.
mysql < /tmp/epcis_schema.sql

# Tell the MySQL daemon to shutdown.
mysqladmin shutdown

# Wait for the MySQL daemon to exit.
wait $mysql_pid

# create a tar file with the database as it currently exists
tar czvf default_mysql.tar.gz /var/lib/mysql

# the tarfile contains the initialized state of the database.
# when the container is started, if the database is empty (/var/lib/mysql)
# then it is unpacked from default_mysql.tar.gz from
# the ENTRYPOINT /tmp/run_db script

(most of this script was lifted from here: https://gist.github.com/pda/9697520)

Here is the files/run_db script I created:

# start db

set -e
set -x

# first, if the /var/lib/mysql directory is empty, unpack it from our predefined db
[ "$(ls -A /var/lib/mysql)" ] && echo "Running with existing database in /var/lib/mysql" || ( echo 'Populate initial db'; tar xpzvf default_mysql.tar.gz )

/usr/sbin/mysqld

Finally, the Dockerfile to bind them all:

FROM mysql
MAINTAINER  (me) <email>

# Copy the database schema to the /data directory
ADD files/run_db files/init_db files/epcis_schema.sql /tmp/

# init_db will create the default
# database from epcis_schema.sql, then
# stop mysqld, and finally copy the /var/lib/mysql directory
# to default_mysql_db.tar.gz
RUN /tmp/init_db

# run_db starts mysqld, but first it checks
# to see if the /var/lib/mysql directory is empty, if
# it is it is seeded with default_mysql_db.tar.gz before
# the mysql is fired up

ENTRYPOINT "/tmp/run_db"

So, I cd'ed to my mdir directory (which has the Dockerfile along with the files directory). I then run the command:

docker build --no-cache .

You should see output like this:

Sending build context to Docker daemon 7.168 kB
Sending build context to Docker daemon 
Step 0 : FROM mysql
 ---> 461d07d927e6
Step 1 : MAINTAINER (me) <email>
 ---> Running in 963e8de55299
 ---> 2fd67c825c34
Removing intermediate container 963e8de55299
Step 2 : ADD files/run_db files/init_db files/epcis_schema.sql /tmp/
 ---> 81871189374b
Removing intermediate container 3221afd8695a
Step 3 : RUN /tmp/init_db
 ---> Running in 8dbdf74b2a79
+ mysql_install_db
2015-03-19 16:40:39 12 [Note] InnoDB: Using atomics to ref count buffer pool pages
...
/var/lib/mysql/ib_logfile0
 ---> 885ec2f1a7d5
Removing intermediate container 8dbdf74b2a79
Step 4 : ENTRYPOINT "/tmp/run_db"
 ---> Running in 717ed52ba665
 ---> 7f6d5215fe8d
Removing intermediate container 717ed52ba665
Successfully built 7f6d5215fe8d

You now have an image '7f6d5215fe8d'. I could run this image:

docker run -d 7f6d5215fe8d

and the image starts, I see an instance string:

4b377ac7397ff5880bc9218abe6d7eadd49505d50efb5063d6fab796ee157bd3

I could then 'stop' it, and restart it.

docker stop 4b377
docker start 4b377

If you look at the logs, the first line will contain:

docker logs 4b377

Populate initial db
var/lib/mysql/
...

Then, at the end of the logs:

Running with existing database in /var/lib/mysql

These are the messages from the /tmp/run_db script, the first one indicates that the database was unpacked from the saved (initial) version, the second one indicates that the database was already there, so the existing copy was used.

Here is a ls -lR of the directory structure I describe above. Note that the init_db and run_db are scripts with the execute bit set:

gregs-air:~ gfausak$ ls -Rl mdir
total 8
-rw-r--r--  1 gfausak  wheel  534 Mar 19 11:13 Dockerfile
drwxr-xr-x  5 gfausak  staff  170 Mar 19 11:24 files

mdir/files:
total 24
-rw-r--r--  1 gfausak  staff   126 Mar 19 11:14 epcis_schema.sql
-rwxr-xr-x  1 gfausak  staff  1226 Mar 19 11:16 init_db
-rwxr-xr-x  1 gfausak  staff   284 Mar 19 11:23 run_db
poroszd
  • 592
  • 4
  • 12
Greg
  • 6,571
  • 2
  • 27
  • 39
  • 3
    Hey Gary, first thanks for help me. I followed your instructions, but when I execute docker build --no-cache . , I have a permission error for the ``init_db`` script. – Marcus Gomes Mar 19 '15 at 18:54
  • I already try to fix that by giving permissions in the ``RUN`` command and the problem is apparently solved. But then when I execute ``docker run`` I have the same problem with the ``run_db`` script. – Marcus Gomes Mar 19 '15 at 19:07
  • 1
    init_db and run_db need to be 0755 permission (they are scripts, they need to have the execute bit set. Do a chmod +x init_db run_db – Greg Mar 19 '15 at 19:16
  • I updated the answer with a directory listing showing the permissions on each of the files. Note: chmod +x files/*_db will accomplish the permissions needed. – Greg Mar 19 '15 at 19:23
  • Thanks Greg, this is solved my problem. I just have a observation in the ``init_db`` script. When mysql executes ``mysql < /tmp/epcis_schema.sql`` no database is created, so mysql will raise an error. The fix is very easy, it is just create a database ``mysql -e "CREATE DATABASE db_name"`` and then add the schema as root ``mysql -u root epcis < /tmp/epcis_schema.sql``. Anyway, thank you very much for the help :). – Marcus Gomes Mar 19 '15 at 20:51
  • 2
    @Greg I tried the steps above however I am getting an error ( [ERROR] Fatal error: Please read "Security" section of the manual to find out how to run mysqld as root! ). Any suggestions ? – Binish Mookken Aug 22 '16 at 11:17
  • What's the point of doing the whole tar dance? Couldn't you just initialize the database from epcis_schema.sql if it's not initialized already? Why do we need the intermediate step of copying the initialized result out to a compressed tar file and then untarring it later? Shouldn't it never be empty anyway since `init_db` will put files in there to create the default? – doliver Nov 07 '16 at 20:10
  • good point. i guess i like tar? there is definitely more than one way to do this. in this example i start mysqld with the data directories in place. you could have two different startup logics, one for initial data, one for pre existing data. i wanted to have a single mysqld startup for both. Also, I thought it would be nice if the initial data was 'static' rather than being created...but, just as easily could have called the create script every time i need initialized data. – Greg Nov 07 '16 at 21:43
  • Newer versions of mysql want to start like this: /usr/sbin/mysqld -u mysql – vcardillo Apr 25 '17 at 21:52
  • Though this works. Database script is NOT application code. So you should avoid creating an image which does database initialization. Instead use automated scripts to manage database in a regular MySQL container. https://vijayt.com/post/running-mysql-docker-container-digitalocean/ – vijayst Dec 14 '17 at 05:05
  • 2
    Better solution from 2016 onward: copy the file to `/docker-entrypoint-initdb.d` directory. See https://github.com/docker-library/postgres/issues/193#issuecomment-244454736 – Alireza Feb 21 '20 at 16:40
89

Another way based on a merge of serveral responses here before :

docker-compose file :

version: "3"
services:
    db:
      container_name: db
      image: mysql
      ports:
       - "3306:3306"  
      environment:
         - MYSQL_ROOT_PASSWORD=mysql
         - MYSQL_DATABASE=db

      volumes:
         - /home/user/db/mysql/data:/var/lib/mysql
         - /home/user/db/mysql/init:/docker-entrypoint-initdb.d/:ro

where /home/user.. is a shared folder on the host

And in the /home/user/db/mysql/init folder .. just drop one sql file, with any name, for example init.sql containing :

CREATE DATABASE mydb;
GRANT ALL PRIVILEGES ON mydb.* TO 'myuser'@'%' IDENTIFIED BY 'mysql';
GRANT ALL PRIVILEGES ON mydb.* TO 'myuser'@'localhost' IDENTIFIED BY 'mysql';
USE mydb
CREATE TABLE CONTACTS (
    [ ... ]
);
INSERT INTO CONTACTS VALUES ...
[ ... ]

According to the official mysql documentation, you can put more than one sql file in the docker-entrypoint-initdb.d, they are executed in the alphabetical order

phico
  • 1,303
  • 11
  • 12
55

The other simple way, use docker-compose with the following lines:

mysql:
  from: mysql:5.7
  volumes:
    - ./database:/tmp/database
  command: mysqld --init-file="/tmp/database/install_db.sql"

Put your database schema into the ./database/install_db.sql. Every time when you build up your container, the install_db.sql will be executed.

Nekvin
  • 83
  • 1
  • 11
Balázs Zámbó
  • 615
  • 6
  • 2
  • 1
    After struggling a lot. You saved my life. This worked. Thanks a lot. – Gopal Mishra Nov 06 '20 at 16:12
  • 2
    I was shocked by the long answer above with creating an own container image only to have a db setup initially. Then I stumbled upon your answer and I was thrilled. Thanks mate! :) – LukeFilewalker Dec 24 '20 at 10:27
  • This would execute each time you start the container using docker-compose up. ideally you would want init script to be executed only once. See the answer by @phico – Bojan Hrnkas Apr 29 '22 at 07:39
  • It does not work when table's engine is MEMORY – hardik Sep 27 '22 at 10:57
  • The official way is to leverage ```/docker-entrypoint-initdb.d``` https://hub.docker.com/_/mysql, but I haven't managed it yet for my schema and config. – Gleichmut Apr 25 '23 at 16:37
35

I've tried Greg's answer with zero success, I must have done something wrong since my database had no data after all the steps: I was using MariaDB's latest image, just in case.

Then I decided to read the entrypoint for the official MariaDB image, and used that to generate a simple docker-compose file:

database:
  image: mariadb
  ports:
     - 3306:3306
  expose:
     - 3306
  volumes:
     - ./docker/mariadb/data:/var/lib/mysql:rw
     - ./database/schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro
  environment:
     MYSQL_ALLOW_EMPTY_PASSWORD: "yes"

Now I'm able to persist my data AND generate a database with my own schema!

mrArias
  • 1,008
  • 11
  • 7
  • 1
    Hi, I'm trying also do the same, but script is never executed. Is there any other parameter which needs to be provided? I've just added this volume for mariadb - ./database/schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro. Thanks – bilak Apr 08 '16 at 08:04
  • 1
    Note: the script will be executed only once, during container initialization. If you later add a new script it wouldn't executed. – ALex_hha May 17 '17 at 12:32
  • A remainder: If you has dumped a database using mysqldump, you would need defining a ```MYSQL_DATABASE=xxxx``` environment variable or adding the line ```use xxxx;``` at the top of your *.sql file – BeardOverflow Jan 25 '21 at 10:14
27

After Aug. 4, 2015, if you are using the official mysql Docker image, you can just ADD/COPY a file into the /docker-entrypoint-initdb.d/ directory and it will run with the container is initialized. See github: https://github.com/docker-library/mysql/commit/14f165596ea8808dfeb2131f092aabe61c967225 if you want to implement it on other container images

JDWatkins
  • 271
  • 3
  • 2
  • 1
    This is the correct answer for current versions of the official Docker MSQL image. – NaN May 25 '18 at 11:36
14

Since I struggled with this problem recently, I'm adding a docker-compose file that really helped me:

version: '3.5'

services:
  db:
    image: mysql:5.7
    container_name: db-container
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - "./scripts/schema.sql:/docker-entrypoint-initdb.d/1.sql"
      - "./scripts/data.sql:/docker-entrypoint-initdb.d/2.sql"
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: test
      MYSQL_USER: test-user
      MYSQL_PASSWORD: password
    ports:
      - '3306:3306'
    healthcheck:
      test: "/usr/bin/mysql --user=root --password=password --execute \"SHOW DATABASES;\""
      interval: 2s
      timeout: 20s
      retries: 10

You just need to create a scripts folder in the same location as the docker-compose.yml file above.

The scripts folder will have 2 files:

  • schema.sql: DDL scripts (create table...etc)
  • data.sql: Insert statements that you want to be executed right after schema creation.

After this, you can run the command below to erase any previous database info (for a fresh start):

docker-compose rm -v -f db && docker-compose up
Eduardo Briguenti Vieira
  • 4,351
  • 3
  • 37
  • 49
11

The easiest solution is to use tutum/mysql

Step1

docker pull tutum/mysql:5.5

Step2

docker run -d -p 3306:3306 -v /tmp:/tmp  -e STARTUP_SQL="/tmp/to_be_imported.mysql" tutum/mysql:5.5

Step3

Get above CONTAINER_ID and then execute command docker logs to see the generated password information.

docker logs #<CONTAINER_ID>
Community
  • 1
  • 1
mainframer
  • 20,411
  • 12
  • 49
  • 68
  • Have you gotten this to successfully run? I've been fiddling with tutum/mysql for weeks with no success. – Iammesol Dec 29 '15 at 05:26
  • 2
    Yes. I use `tutum/mysql:5.5` and it successed. `docker run -d -p 3306:3306 -v /tmp:/tmp -e MYSQL_PASS="admin" -e STARTUP_SQL="/tmp/mysqldump.mysql" tutum/mysql:5.5`. You can always execute command `docker logs CONTAINER_ID` to see the error messages if you met issues. – mainframer Dec 29 '15 at 05:29
  • i can't figure out where the path `/tmp/to_be_imported.mysql` is coming from. is that a path on the host OS? where is a `COPY` or `ADD` to put things onto the container's filesystem? – Randy L Feb 05 '16 at 22:32
  • @the0ther you don't need `COPY`/`ADD` because /tmp host directory is mounted to /tmp of the container. Look closely on the command there is the part `-v /tmp:/tmp`. Personally I wouldn't mount a volume simply to make the file available to the container but I guess it is a personal choice. – Willa Mar 01 '18 at 08:48
  • 3
    To be fair, you shouldn't use non-official images, unless you want [this](https://arstechnica.com/information-technology/2018/06/backdoored-images-downloaded-5-million-times-finally-removed-from-docker-hub/) to happen again. – Dragas Nov 20 '18 at 08:47
  • 1
    The github page of this repo says: "This repo is deprecated: we are not going to maintain it anymore." – Tarun Gupta Nov 06 '19 at 09:46
8

For the ones not wanting to create an entrypoint script like me, you actually can start mysqld at build-time and then execute the mysql commands in your Dockerfile like so:

RUN mysqld_safe & until mysqladmin ping; do sleep 1; done && \
    mysql -e "CREATE DATABASE somedb;" && \
    mysql -e "CREATE USER 'someuser'@'localhost' IDENTIFIED BY 'somepassword';" && \
    mysql -e "GRANT ALL PRIVILEGES ON somedb.* TO 'someuser'@'localhost';"

or source a prepopulated sql dump:

COPY    dump.sql /SQL
RUN     mysqld_safe & until mysqladmin ping; do sleep 1; done && \
        mysql -e "SOURCE /SQL;"
RUN     mysqladmin shutdown

The key here is to send mysqld_safe to background with the single & sign.

sgohl
  • 391
  • 3
  • 17
  • I don't think this approach works, since the `mysql` base image contains a `VOLUME` declaration that prevents the image from containing preloaded data. – David Maze Feb 08 '22 at 10:30
1

After to struggle a little bit with that, take a look the Dockerfile using named volumes (db-data). It's important declare a plus at final part, where I mentioned that volume is [external]

All worked great this way!

version: "3"

services:

  database:
    image: mysql:5.7
    container_name: mysql
    ports:
      - "3306:3306"
    volumes:
      - db-data:/docker-entrypoint-initdb.d
    environment:
      - MYSQL_DATABASE=sample
      - MYSQL_ROOT_PASSWORD=root

volumes:
  db-data:
    external: true
Tatsuyuki Ishi
  • 3,883
  • 3
  • 29
  • 41
0

Below is the Dockerfile I used successfully to install xampp, create a MariaDB with scheme and pre populated with the info used on local server(usrs,pics orders,etc..)

FROM ubuntu:14.04

COPY Ecommerce.sql /root

RUN apt-get update \
 && apt-get install wget -yq \
 && apt-get install nano \
 && wget https://www.apachefriends.org/xampp-files/7.1.11/xampp-linux-x64-7.1.11-0-installer.run \
 && mv xampp-linux-x64-7.1.11-0-installer.run /opt/ \
 && cd /opt/ \
 && chmod +x xampp-linux-x64-7.1.11-0-installer.run \
 && printf 'y\n\y\n\r\n\y\n\r\n' | ./xampp-linux-x64-7.1.11-0-installer.run \
 && cd /opt/lampp/bin \
 && /opt/lampp/lampp start \
 && sleep 5s \

 && ./mysql -uroot -e "CREATE DATABASE Ecommerce" \
 && ./mysql -uroot -D Ecommerce < /root/Ecommerce.sql \
 && cd / \
 && /opt/lampp/lampp reload \
 && mkdir opt/lampp/htdocs/Ecommerce

COPY /Ecommerce /opt/lampp/htdocs/Ecommerce

EXPOSE 80
Paddy Popeye
  • 1,634
  • 1
  • 16
  • 29