How to Conditionally Create a File in a Dockerfile Build
Building from my previous post of how to conditionally copy a file in a Docker image build from a Dockerfile, I am going over how to conditionally create within a Docker image build in a Dockerfile. For example:
- If a specified file is not in the working directory, create a file.
- Otherwise, use the specified file.
The main reason I want to solve this problem is I want to have a simple Dockerfile for existing Ruby on Rails projects, but also to create new Ruby on Rails projects.
With the way Docker uses images, I will always create a new Docker image based on the project, but I would like to have one goto Dockerfile for any Ruby on Rails project. For example:
- I want to take an existing app and put it in a Docker container.
- I want to start a new app and have it inside a Docker container.
If you want to follow along, the approach I took with this solution will require:
- Install Docker Community Edition
I did development on a UNIX based system (such as macOS or Ubuntu),
mainly for bash utility:
In my example, I will create a new Ruby Gemfile. Otherwise, use the existing Gemfile (copied over in the build process).
If there needs to be a new Gemfile, create the following Gemfile with contents:
It’s the same as the default Gemfile I used in my Docker Rails article.
The approach I took to solve the problem of creating a Gemfile if it is not in the folder, or using the existing one is to:
- Test for existence of the Gemfile inside the working directory.
- If the Gemfile is not there, write a prebuilt Gemfile.
I used Bash for my solution as the system Docker is building will have Bash available.
Testing for File in Bash
The Bash snippet to test for the Gemfile existence is:
do something section is where I will have the script write the
file contents, but how?
Creating Files with: echo
Using another Unix command,
echo, we can write the contents of a
file in the form:
So, combining the above, the function becomes:
There is a slight difference between
- The former,
>, will overwrite the file with a new one.
>>, will append to the existing file.
Small difference in syntax will have large consequences.
Making Script Executable: chmod
If we write the script file to disk out and add an execution bit to it:
So, the script does what we want, how do we get this script inside the Dockerfile?
One way is to write the file and have the Dockerfile copy the script over, the commands inside the Dockerfile:
This approach works, especially if the script file is large or complex.
One downside to this approach is there is another file maintain with the Dockerfile. Every project will have to include the Dockerfile and the additional file.
Another approach is to embed the script within the Dockerfile and use
RUN command to create a script to create the file. (Think
how meta that is!)
I prefer this approach
I will take advantage of the
echo command from earlier, a shortened
version in a Dockerfile:
In this case, I chose to have the
RUN command write the script out as:
Having the whole script in a single line is long and hard to read or change, but once it’s perfected, there only needs to be a single file to keep around, the Dockerfile.
Can I Do Better?
Actually, like all good computer scientist, I have to ask: “Can we do better?” (than having the whole file on one line)?
When I look at the Dockerfile
I notice that the
\ operator separates multiple line arguments on
Let’s see if that works for strings as well:
Let’s see by building it out:
So, wow, we can embed a whole shell script inside the Dockerfile and have it on multiple lines, making it more readable. Now this is bootstrapping a Docker image through the Dockerfile!
The section below is the Dockerfile I used for reference:
The file is also downloadable here
To build and test, just run:
I wanted to have a Dockerfile that I can use to:
- Create a new Rails project
- Dockerize and existing Rails project
By having a shell script to create a script to create a new Gemfile, I have solved the former while keeping compatibility with the latter using a file copy trick from my previous article.
So, now I will be using this Dockerfile for my projects, either new or existing!