Defining a Dockerfile

This is a file specification with instructions for Docker to create an image. The instructions are clear and allow you to share that file on several servers so that Docker builds the image.

Our Dockerfile will have three parts:

  • The header, which indicates the base image and author
  • The instructions to build the image
  • The instructions to run the image

There are many more instructions that we'll probably not cover. To get to know Dockerfile better, head over and read the documentation. This is an introduction that will actually help you go a long way. Here's our first Dockerfile:

FROM node:alpine
MAINTAINER Diogo Resende

ADD imagini /opt/app

WORKDIR /opt/app
RUN npm i

CMD [ "node", "/opt/app/imagini" ]

The empty lines were added for clarity only. The first two instructions define the base image (FROM) and the author (MAINTAINER). The next three add our imagini folder to the /opt/app folder inside the image. Then, we change directory to that folder and run npm i to install the required dependencies. Finally, the last instruction indicates how to run the image later on.

Save this to a file called Dockerfile and place it in an empty folder. Then, grab our microservice and place it inside a folder called imagini, inside this newly created folder:

We can now try to build our image using the docker build command. Let's create the image, assign the name of our service, and indicate an initial version:

Let's check the list of images available:

 

Let's just start a container and see what happens. To do that, we use the docker run command and pass our image name and version. We don't need to specify what to run inside the image since we defined that on the Dockerfile, but we could change that:

 

Well, I expected that to happen. I did this deliberately so that you never forget that dependencies should be held carefully. Just as you don't push dependencies to a git repository, you don't push dependencies to an image. Instead, you should install them inside the image. That's one of the reasons the image built so quickly because NPM assumed that everything was all right.

Let's change our Dockerfile and create a new image, but before that, let's go inside the image and look at it. To do that, let's run the container specifying another command to execute. We also need to specify a few more parameters, namely -i and -t for an interactive terminal. Let's run a console (we don't have bash in Alpine, so we'll stick with sh):

 

This is what's inside the /opt/app folder inside the image. There are a couple of things we don't need to add to, like the test and coverage folders. We can just stick to the microservice, the dependencies file, and the settings file:

FROM node:alpine
MAINTAINER Diogo Resende

ADD imagini/imagini.js /opt/app/imagini.js
ADD imagini/package.json /opt/app/package.json
ADD imagini/settings.json /opt/app/settings.json

WORKDIR /opt/app
RUN npm i

CMD [ "node", "/opt/app/imagini" ]

The ADD instruction can be used with files, not just folders. This will keep our image as slim as possible. Let's run it and assign a new version:

 

That looks better, despite the error. NPM tried to install sharp but our image is so slim it doesn't have Python installed. Actually, it's not only Python that will be missing; you'll find there are no build tools for you to compile anything. If you really need a very slim image, you can this way and see what other dependencies you'll need.

For demonstration purposes, let's switch our base image to the standard version:

FROM node
MAINTAINER Diogo Resende

ADD imagini/imagini.js /opt/app/imagini.js
ADD imagini/package.json /opt/app/package.json
ADD imagini/settings.json /opt/app/settings.json

WORKDIR /opt/app
RUN npm i

CMD [ "node", "/opt/app/imagini" ]

See how we change the first line to just node. If you now run the docker build command, Docker will download the standard image and build our image in a single operation:

As we can see, NPM now compiled our sharp dependency.

Note that if you only rely on simple dependencies, you can stick with the Alpine version.

Let's try to run our container again:

Well, we forgot our settings file inside the container. That settings file points to the MySQL server on the local address, which is actually wrong because the container has its own interface.

To make our image free of passwords and a little bit usable, we need to have our settings file outside the image, and every time we want to run the image, we indicate the settings file we want to use. This enables you to have different deployments with different settings sharing the same base image.

But, because of how Docker mounts, we need to have the file there already, even if it's empty, to avoid Docker mounting as a directory. So, let's move the settings file to the parent folder, and create an empty settings file where it was before. You should end up with a structure similar to the following screenshot:

So, we should keep the same image instructions, but now with an empty settings file:

FROM node
MAINTAINER Diogo Resende

ADD imagini/imagini.js /opt/app/imagini.js
ADD imagini/package.json /opt/app/package.json
ADD imagini/settings.json /opt/app/settings.json

WORKDIR /opt/app
RUN npm i

CMD [ "node", "/opt/app/imagini" ]

Now, let's create a new version:

 

There are two things you should know about building an image:

  • Since we haven't published the image to the public, and we're still testing, we could overwrite our image version and avoid incrementing the revision value. We're doing so just for you to see how the community usually adds new versions.
  • If you read the log lines of the build, you'll notice some lines that contain Using cache, which indicate that Docker found that instruction step in a previous build and is using that instead of rebuilding. If we had the ADD instructions after the dependency installation, you would probably see that installation using cache and taking much less time to build.

To run our container with our image and our settings attached, we need to use the -v, which stands for volume, and allows us to mount a folder or a file from our local filesystem to the container filesystem. Assuming we're in a console on our root folder from the preceding screenshot, we should run the following command:

docker run -v $(pwd)/settings.json:/opt/app/settings.json imagini:0.0.3

Docker enforces us to use full paths, so I just added another bash interpolation to get the current working directory (pwd). Let's run it:

Well, that's another property of containers; they run isolated and have their own network interface. That interface is attached to a virtual switch created by Docker, which has no outside connectivity unless you say so.

We're using localhost as the database server hostname, which, in this case, points to the container itself. We need to change this to the local address of our system. Since our local system may have a changing IP address, Docker has a special DNS address, which is host.docker.internal, that points to our host.

I'm changing my settings file to something like the following lines. Change it according to your previous configuration:

{
"db": "mysql://root:[email protected]/imagini"
}

If we run it again, we should be able to finally start our container:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset