Learning Docker: Creating a Dockerfile

This post builds off of two previous posts of mine:

In one, I discussed how Docker works at a high level. If you’ve never used Docker before, I recommend reading that overview first. I’m going to assume you’re familiar with the Docker ecosystem in this post.

In the second, I built a basic React app from scratch with Webpack. I’m going to use that basic React app in this post to create a Docker image and run a container from it.

A container is an instance of an image. In order to run a container with Docker, we need to create an image. What exactly is an image?

An image is a blueprint that tells Docker what to build in the container when it runs. The image needs to contain everything we need to run our application — and I mean everything.

We need a way to give Docker step-by-step instructions on how to:

  1. Build the right environment for our application to operate in
  2. Install our application
  3. Run the application

A Dockerfile is just that; step-by-step instructions for Docker to build an image based on the project directory. To create a Dockerfile, create a file called Dockerfile (no file extension) in your project’s root directory.

Then, use the various instruction keywords provided by Docker to walk it through building the image. Here are the contents of the Dockerfile for my basic React app:

If you’ve used Node before, some of these instructions should look familiar. You can see where we’re telling Docker to install our modules (RUN npm install) and to start our application with npm start.

The stuff in between is mostly copying files from the directory into the container. I’ll go through each instruction and explain what each is doing.

FROM node:latest

This instruction accomplishes everything we need to complete step one. The FROM instruction sets the base image for our container. Here, we are setting it to the latest version of the default Node image.

To make things easy, the default Node image includes Debian, a version of Linux. We now have an operating system and a Node environment in one line of code.

RUN mkdir /basic-react

RUN instructions run commands in a shell within the container. Here, we’re creating our root directory in the container.

WORKDIR /basic-react

This instruction isn’t necessary, but it will make the following code cleaner. WORKDIR sets the working directory in the container, meaning we can reference it with a . going forward.

COPY package.json .

We’ve built our Node environment. Next, we need to install our application. The package.json file in our project’s directory contains all of the instructions for Node to install its dependencies. With the COPY instruction, we’re telling Docker to copy the package.json file from the project directory into the root directory in the container.

(If we deleted the WORKDIR instruction, we would write COPY package.json /basic-react instead)

RUN npm install

Now that we have a package.json file, we can tell Docker to install the dependencies.

COPY . .

After the dependencies are install, we copy everything in our project’s directory into the root directory in our container. The application is now installed and ready to run.

EXPOSE 8080

The EXPOSE instruction tells Docker to open a port on our container. I used Webpack’s development server for this project, and it’s set to port 8080. With this instruction, Docker will open port 8080 on the container and wire it to port 8080 on the host machine.

CMD [“npm”, “start”]

Finally, the CMD instruction sets the command to execute when running the container. The command is separated into items in an array where the executable, npm, is followed by its parameters, start.

Next, we’ll build the image and run the container, but first I want to include a note for anyone else using the webpack-dev-server. Because I’m using Webpack’s server, I had to add an additional --host flag to my npm start command to be able to access the development server from outside of the container.

In my package.json file, I have this under "scripts":

Running a Container

To build the image, all we have to do is run the following command from within the project directory:

The -t flag creates a tag for the image, which isn’t necessary but it’s recommended to help organize your images. The . at the end of the command is important. That’s telling Docker to build the image from the current directory.

When you execute this command, Docker will run through the Dockerfile and build the image. To run a container based on the image we just created, run the following command:

This is telling Docker to run a container based off the latest version of our image. The -p flags tells Docker to publish our container’s port 8080 to the host machine’s port 8080.

Now, if we open up http://localhost:8080 in the browser, we can see our app!

If you use VS Code, I recommend installing the Docker extension to help manage your images and containers. With the extension, you can build an image and run a container all from VS Code. You don’t have to worry about remembering the above commands every time you spin up a container.