Michael
Michael

Reputation: 422

How to exec init script only once after docker run but before entrypoint?

I am learning docker these days, and starts with building mysql image by myself.

Dockerfile:

FROM centos
MAINTAINER Michael
ENV REFRESHED_AT 2016-07-29
RUN yum install -y mysql mariadb-server
VOLUME /var/lib/mysql
ENTRYPOINT ["/usr/libexec/mysqld", "--user=root"]
EXPOSE 3306

docker run command

docker run -d --name mysql -v /root/docker/mysql/data:/var/lib/mysql -p 3306:3306 michael/mysql

This gave an error because I have to exec mysql_install_db first to init DB. But I could not add RUN mysql_install_db in the Dockerfile since I want to use Volume as external data store.

So how should I do this ?

I know there's an official image named mysql. I just want to do this as practice.


UPDATE: Thanks to @Pieter . I finally did this by using his solution which is provide another entrypoint.sh combines init & start scripts then make it as ENTRYPOINT in Dockerfile :

FROM centos
MAINTAINER Michael
ENV REFRESHED_AT 2016-07-29
RUN yum install -y mysql mariadb-server
VOLUME /var/lib/mysql
COPY entrypoint.sh /usr/local/bin/
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3306

entrypoint.sh

#!/bin/bash

if [ ! -d "/var/lib/mysql/mysql" ]; then #check whether the DB is initialized.
    echo 'Initializing database'
    mysql_install_db
    echo 'Database initialized'
fi

/usr/libexec/mysqld --user=root

docker run

docker run -d --name mysql -v /root/docker/mysql/data:/var/lib/mysql -p 3306:3306 michael/mysql

And this gives a generic solution for such scenario.

Upvotes: 7

Views: 16391

Answers (2)

deadcoder0904
deadcoder0904

Reputation: 8693

There's also another nice way to do it which I found from this blog post so full credit goes to it.

But I got an error while running ENTRYPOINT command:

Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "entrypoint.sh": executable file not found in $PATH: unknown

I tried implementing it because I needed a way to initialize my *.sqlite database for the first time. And then the rest of the times I needed a way to restore backups using litestream. I tried using entrypoint.sh but it gave weird errors.

So I just copied that code-block in my run.sh at the start.

run.sh

#!/bin/bash
set -e

# Set the directory of the database in a variable
DB_PATH=/data/users.prod.sqlite

# Copied from https://pranavmalvawala.com/run-script-only-on-first-start-up
CONTAINER_FIRST_STARTUP="CONTAINER_FIRST_STARTUP"
if [ ! -e /data/$CONTAINER_FIRST_STARTUP ]; then
    # Place your script that you only want to run on first startup.
  echo 'Initialize database first time only'
    touch /data/$CONTAINER_FIRST_STARTUP

    # Only install dependencies for drizzle migration. Those are not bundled via `next build` as its optimized to only install dependencies that are used in next.js app
    echo "Installing production dependencies"
    cd scripts
    pnpm config set store-dir ~/.pnpm-store
    pnpm fetch
    pnpm install --prod --prefer-offline
    cd ..

    echo "Creating '/data/users.prod.sqlite' using bind volume mount"
    pnpm db:migrate:prod & PID=$!
    # Wait for migration to finish
    wait $PID
fi

# Restore the database if it does not already exist.
if [ -f $DB_PATH ]; then
    echo "Database already exists, skipping restore"
else
    echo "No database found, restoring from replica if exists"
    # Restore backup from litestream if backup exists
    litestream restore -if-replica-exists $DB_PATH
fi

echo "Starting production server..."
# Run litestream with your app as the subprocess.
exec litestream replicate -exec "node server.js"

The first code-block in the if statement above is all you need to run any script just once.

The final project is at https://github.com/deadcoder0904/easypanel-nextjs-sqlite/

Upvotes: 0

Pieter
Pieter

Reputation: 2783

I'm no expert but for all I know there is no way to run some script before ENTRYPOINT as ENTRYPOINT is literally the first thing your container runs.

What you can do is add a custom script to your docker container that contains both the mysql_install_db and mysqld instructions and use that as the entrypoint.

So your dockerfile might look like this.

FROM centos
MAINTAINER Michael
ENV REFRESHED_AT 2016-07-29
RUN yum install -y mysql mariadb-server
VOLUME /var/lib/mysql
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 3306

entrypoint.sh would simply look like this.

/usr/libexec/mysql_install_db --user=root
/usr/libexec/mysqld --user=root

Note that the same technique is also used in the official mariadb docker image. See https://github.com/docker-library/mariadb/tree/d969a465ee48fe10f4b532276f7337ddaaf3fc36/10.1

Note that the official image combines ENTRYPOINT with CMD. As you're learning docker you might want to take a look at https://www.ctl.io/developers/blog/post/dockerfile-entrypoint-vs-cmd/ for more information on how ENTRYPOINT and CMD can be used together.

UPDATE Assuming that mysql_install_db simply creates some files under /var/lib/mysql you should be able to run mysql_install_db as part of your docker build. (This doesn't mean that you should though -> see official mariadb image).

The docker run command initializes a newly created volume with any data that exists at the specified location within the base image. See https://docs.docker.com/engine/reference/builder/#/volume

Upvotes: 6

Related Questions