Getting started with Docker

Barack Mukelenga
10 min readMay 15, 2022

When we are new to a given project, we sometimes come across this weird situation where the app seems to work very well on our teammate’s computer, but for us, even after installing all the dependencies we still can’t have the app up and running.

This might happen due to several problems like versions of dependencies, the conflict between programs running on the same computer such as library dependencies or ports…

This is when Docker comes into play. In this blog post, we are going to see how we can solve these problems by dockerizing an application. But before going any further, let’s try to understand what Docker is.

What is Docker

Docker is an open-source containerization platform. It enables developers to package applications into containers — standardized executable components combining application source code with the operating system (OS) libraries and dependencies required to run that code in any environment.

When learning Docker it’s important to have a bit of knowledge using some Linux commands because Docker is built on top of basic Unix/Linux OS concepts. Well, we will try to cover some of the important commands but if you wish to go deep into this you can find some more here.

First, we will need to install Docker itself. For this let’s go to the Docker official website and download the appropriate desktop app for your operating system.
After the installation is complete, let’s find the installed app and open it to start Docker.

The application that we will Dockerize today is a React App, however, it does not need to be a react app since what we’ll be covering applies to any other project.

Let us clone the project using git by running the following command into your terminal from your favorite location on your computer:

git clone https://github.com/barackm/react-dockerized-app.git

This will create a directory called react-dockerized-app. Open this directory in the terminal and install all the dependencies by running the following command assuming that you have the latest Node Js version installed on your computer, if not, you can find more on how to install Node Js here https://nodejs.org/en/.

Install all the dependencies by running the npm install .

After all the dependencies are installed, we can now view the app by running the following command:

npm start

This will start a development server that will serve our App, if this operation is successful you’ll be able to see the app by going to http://localhost:3000/ using your favorite Navigator. If everything is okay, you should be able to see this screen.

Now that our up is up and running, let’s see how we can dockerize it. But before we start Dockarizing our application we need to understand these three core concepts:

  • Docker Image: A Docker image is a file used to execute code in a Docker container. A Docker image actually acts as instructions to build a Docker Container. But it also serves as the starting point when using Docker. It contains everything our application needs in order to run, this includes a cut-down OS, application code, tools, libraries, dependencies, environment variables, and other files needed that make our Application run. You can find further explanations about Docker Images here.
  • Dockerfile: A Dockerfile is a file that we create that holds all the necessary instructions to build a Docker image, but also to run the application.
  • Docker Container: A Docker container is like a virtual machine in the sense that it provides an isolated environment for executing an application, Containers can be stopped and restarted just like virtual machines.

Dockerfile

Now let’s start by creating a file Dockerfile from the root of our project.

Let’s write the below instructions into this file.

FROM node:14.16.0-alpine3.13WORKDIR /appCOPY package*.json ./RUN npm installCOPY . .EXPOSE 3000CMD ["npm", "start"]

Let’s try to understand all the above instructions:

  • FROM: When creating a Docker image, we always need a base image that has at least a lightweight operating system, in our case we have chosen to work with node:14.16.0-alpine3.13 which is a Linux distribution Alpine that comes with Node Jsalready installed. With this, we will no longer need to install Node Js because our application needs node js to run.
    You can find the list of available base Images here.
  • WORKDIR: This is the working directory of our application, remember, we have required an OS that has a file system so it is just like a small computer which means we need to create a folder where our project source code will be kept, in this case, we call it app .
  • COPY: The COPY command is just used to specify files or folders we need to copy to our new working directory in the image, we can copy a single file, a folder, or even a full working directory by adding a .
    This command expects two arguments, the first argument is what we need to copy, and the second argument is the location where we need to copy to.
    EX: package*.json Here we can see that we first copy all the files that start with package and end with .json, from a Node Js based project this will include: package.json, package-lock.json .
    The package.json has the list of all the dependencies that our application depends upon.
    The package-lock.json keeps track of the correct versions of these dependencies. This is why we start by copying these two files first in order to install all the dependencies before we can copy the rest of the project.
    The second argument is the path to which we need to copy the files, in our case we have already specified the working directory with the command
    WORKDIR app/ that means by just adding the . Docker will then understand that we need to copy the two files to the directory app in our Image.
  • RUN: The RUN command specifies all the commands that we need to run, not inside the Docker Container, but instead, in the build process of the Image. This is then the perfect place to install everything our application needs, for example, we can install Node Js, Git or any other dependencies or tools that our application needs to run .
    But for our React application, we will only need to install the dependencies inside package.json by running the command: npm install . Note that here we are using npm which requires Node Js , this is the reason why we have chosen a Docker Image that comes with Node Js already installed so that we may not need to install it manually. For example, if we might have chosen ubuntu as our base Image, we then could have been obliged to install node into the Image first before we can install the dependencies using npm .
  • COPY . . : This command will copy all the files and folders from the current project working directory, in our case if you cloned our React Project repo without changing the name it should be react-dockerized-app into the Docker Image project directory which is app .
  • EXPOSE: The EXPOSE command will specify the port to which our application will start inside the Container , note that this will not be the same port from the Host.
  • CMD: Here we can specify the command that will be run after the Image is built when starting a container. Remember that the CMD command will only be written once in the Dockerfile , if you have multiple CMD , only the last one will be executed.
    CMD [‘npm’, ‘start’] will then start our application on port 3000 that we specified in the EXPOSE command inside the Container.

Now that we have an idea of what is going on inside our Dockerfile , let’s see what is the command we can run to tell Docker to build our Image following those specified instructions, but before we do that we have to understand that the npm install command will create a directory called node_modules this directory contains all the dependencies that will be installed, and it’s usually so huge and every time we run npm install it will get generated, this is why we don’t need to copy this directory into our Image since the npm install will create it in our Image during the build process, this will make our build process faster.

How does Docker know which files or directories to ignore? Well, this is very simple because it’s quite identical to how we tell Git to ignore some files or directories. This can be done by creating a file called .dockerignore and then specify whatever you want Docker to ignore during the build process. For us, we will only need to ignore the node_modules/ for now.

node_modules/

Building a Docker Image

Now let’s build our image. For this, we need to open our application in the terminal and run the following command from the root of the project, assuming that you have already installed Docker:

docker build -t react-dockerized-app .

  • The build command is used to build an image specified inside the Dockerfile .
  • The -t is short for tag this will just give a tag to the image that you need to build to differentiate it from other builds, because yes, you can have multiple layers for the project or image, so giving tags to these images will help you differentiate them and thus, make it easier to find a version that introduced a problem in case you find one later.
  • The . just tells Docker where to find the Dockerfile that has all the instructions on how to build our image.

After successfully running this command, you should be ready to check if the image is now available by running the command:

docker images

Now that we have our image built, let’s run it inside a container with the following command:

docker run react-dockerized-app

This will start the application on port 3000 in the container, however, if you stop the react server that we started after cloning the repository with
Ctrl + c and refresh the browser on port 3000 : http://localhost:3000 , you won’t be able to access the application anymore even if it’s already running in the container on the same port. This is due to the fact that when exposing a port in the Dockerfile , this is only going to be available inside the running container and not the host that we try to access with http://localhost:3000 . For us to be able to access the application from the host, we need to map the port in the container to a port on the host by adding a parameter -p short for port when starting the container:

docker run -p 5000:3000 react-dockerized-app This tells Docker to start the app on the port 5000 on the host and 3000 inside the container.

Now if you go to http://localhost:5000/ , you will be able to access the application 👏.

You may have realized that it’s not possible to run other commands after we have started a container, but sometimes we may need to run other commands while the app is running in the background, to do this we can use another argument with the docker run which is -d short for detached , to find all the possible arguments that go with the docker run command you can only run docker run --help .

Now to start our application in the detached mode and run other commands, we can stop the current process by pressing Ctrl + c , then start the container with the command:

docker run -d -p 5000:3000 react-dockerized-app This will start the container in the background, to verify that the container has started and see all the running processes we can use the command:

docker ps and docker container ls . This will list all the running containers, it might look like the image below 👇 You will be then able to access the app from the port 5000

To stop a running container we can use the command
docker container stop react-dockerized-app or by using the container ID
docker container stop f7422 . Remember, the container ID will be different from yours.

Volumes

Now imagine when you change something in your source code, having to rebuild the image over and over again, this will really be annoying. This is when volumes come into play.

Docker volumes are file systems mounted on Docker containers to preserve data generated by the running container. The data doesn’t persist when that container no longer exists, and it can be difficult to get the data out of the container if another process needs it.

The volumes can help us then to share the source code with a running container by adding another argument to the docker run command , -v short for volume, you can learn more about volumes here.

docker run -d -p 5000:3000 -v $(pwd):/app react-dockerized-app The command that we have added is -v $(pwd):/app , here the $(pwd) stands for print working directory which is just the same as typing the full path of the project folder. The /app is the location to where we need to copy the content from the $(pwd), with this in place, we can be able to modify the source code, and directly the changes are going to be reflected in the container. To test this, let’s run the command
docker run -p 5000:3000 -v $(pwd):/app react-dockerized-app and go to the project directory /src/App.js and change the <h1> tag content with: Is very easy! and save. After saving the file, we should be able to visit the app on port 5000 and refresh the page, the changes will already be reflected.

Congratulations, you have now Dockerized your first application 👏 👏

--

--

Barack Mukelenga

Hi there, I'm Baraka Mukelenga a software developer specializing in different technologies, currently working as a React and React native developer at Code&Care