Docker for Beginners: What is Docker and How to Create Docker Containers

If you have been in touch with the latest developments in the programming world in the past few years, you would have noticed the loud buzz surrounding Docker and Docker containers. This popularity of docker is not without reason. The introduction of Docker has vastly changed how developers approach application development.

Who wants to be left behind when such a revolutionizing technology hits the programming world? So, today, we are starting a new tutorial series for you to learn how to use Docker for application development. If you are an absolute beginner to Docker, this tutorial series is the right place for you to start.

In the first article of our tutorial series, we are looking to understand what exactly Docker is and why developers love Docker so much. We will also be dockerizing a simple Node.js application to familiarize you with the basic concepts of Docker.

Why wait any longer? Let’s begin!

What is Docker

Docker is a tool that is used to build applications; that is to create, deploy, and run applications through containers.

With a container, all the libraries, and other dependencies required to run an application, are packed as a single package for deployment.

The main goal of containerizing an application is isolating them from other applications running in the same system. This approach ensures applications don’t interfere with the operation of one another and makes application maintenance much easier.

Though containers running in the same system are isolated from one another in execution, they share the same OS kernel. Hence, containers are more lightweight compared to the alternative choice for isolating application execution, virtual machines.

A containerized application running on your Windows OS is guaranteed to run without an issue in another user’s Windows machine despite the change of environment.

Though containers have been long in use before Docker, the introduction of Docker popularized using containers in the developer community. There are two components that are used when dockerizing an application: Dockerfile and Docker Image. Let’s find out what they are.

Dockerfile

Dockerfile is a text file that contains a set of commands required to build a docker image. Dockerfile contains information about the underlying OS, the language, file location, and network ports among other things.

Docker Image

When you run Docker’s build command with a dockerfile in place, a docker image is created based on the dockerfile. They act as templates to create the final docker container. Once created, docker images are static. You can invoke Docker’s run command to create the docker container using a docker image.

Dockerizing a Node.js Application

In this tutorial, we are going to dockerize a Node.js application. We will follow a step by step approach to get the Docker container up and running.

1 - Create the Node.js application 2 - Create the dockerfile 3 - Build the docker image 4 - Create the application container

Before diving into dockerizing our app, you should make sure Docker and Node.js are installed in your system

  • Install Docker on your system—I will not cover how to install Docker in this tutorial, but you can follow Docker documentation and install Docker on your Windows or Ubuntu desktop.
  • Download and Install Node.js from the official website

Create the Node.js Application

Navigate to the new project directory from the command-line and run the following command to create the package.json file which contains information on the application’s dependencies and start script.

npm init -y

Then, install and add Express as a dependency to your application by running this command on the command-line. We will be using Express to create the app.

npm install express --save

This will add the express as a dependency to our package.json file.

Now we can create a Node application with the help of Express.

Create a file named app.js in the project directory and add the following code to the file.

const express = require('express')
const app = express()

app.get('/', (req, res) => {
    res.send("Hello World!")
})

app.listen(3000, () => {
    console.log("Node server has started running")
})

The above code creates a Node server that listens to incoming requests on the port 3000. You can run this command on the command-line to start the Node server.

node app.js

Now go to your browser and follow the URL http://localhost:3000 and you will see the text Hello World! on the page.

We have built a simple Node application for our project. Now let’s move on to creating the dockerfile.

Create the Dockerfile

In the dockerfile, we provide information that is required to build and run our Node app withing the Docker environment.

This includes specifying the language and its version used in the application, setting our project directory as the working directory, copying all the files in the working directory, setting the network port, and specifying which file is the entry point to the application. In more complex applications, you will have to set environment variables and the database URL in the dockerfile as well.

FROM node:latest

WORKDIR /dockerTutorial

COPY . .

RUN npm install

EXPOSE 3000

ENTRYPOINT ["node", "app.js"]
  • FROM command retrieves an OS image, to run our application on a specific OS, from Docker Hub. Docker Hub is a repository of docker images that we can pull to the local environment. We are retrieving an Ubuntu-based image that has installed Node.js. Using “latest” as the Node version pulls an image that has the latest Node version installed.
  • WORKDIR command sets the working directory of the application.
  • COPY command copies files from the current directory (on the command-line) to the working directory that was set in the previous step. You can either specify a file name to copy or use double full stops to copy all the files in the current directory to the working directory.
  • RUN command installs all the dependencies that are required to build the application. This includes all the dependencies specified in the package.json file.
  • EXPOSE command opens up a port from the Docker container to the outside world. This port receives all the requests we send to the Docker container. Port is specifically set to 3000 because it is the port our Node application inside the Docker container uses to listen to requests.
  • ENTRYPOINT specifies how to start the application. Docker joins the array we provide to a single command to start the application. In this case, node app.js.

Building the Docker Image

Use the following command to create the Docker image from the dockerfile.

docker build -t docker-tutorial .

Docker-tutorial is the name of the Docker image. The dot indicates the file path to the project directory, which is where we are currently at in the command-line.

If the OS image specified with the FROM command, node:latest, is not in your device at the moment, it will be pulled from Docker Hub when you run the above command.

After pulling the image, each command in the dockerfile will be executed one by one.

At the end of the execution, if you see the message successfully built, the docker image of the application has been built successfully. Run this command to see the built docker image in the local image repository.

docker images

The output looks like this

Creating the Container

Now we can use the built image to create our Docker container. Use the docker run command to create the container.

docker run -p 8080:3000 docker-tutorial

Here, the numbers 8080 and 3000 indicates the external and internal of the container. External port, 8080, is the port we use to connect to the application through our browser. Internal port, 3000, is the port our application listens for incoming requests. Docker container maps the given external port to the internal port.

Visit the URL http://localhost:8080 on the browser and see if you get the page with Hello World! message that you got when visiting http://localhost:3000 before. If yes, then your Docker container is up and running.

You can use this command to view all the running Docker containers on your device.

docker ps

The command will give you an output like this. We can find the CONTAINER_ID and NAME of the running container here.

Adding Environmental Variables to Your Application

Remember how I mentioned an application with environmental variables requires more instructions in the dockerfile? The value of environmental variable changes with the environment they are running in.

Note how we explicitly mentioned the port our Node app listens to when the server is running. This approach is inflexible and error-prone. In case we run our application in an environment that does not open the port 3000 for the Node server, our application stops operating.

The most appropriate implementation is taking the port number out of the application. Instead, we use a variable name in place of port number and set a value for that variable in the running environment. In our case, the running environment is the Docker container. So, we have to add the port number to the dockerfile as an environment variable.

Let’s see how we can do that.

First, add the environmental variable to our dockerfile with its value. We have to add a new command to the dockerfile to accomplish this.

FROM node:latest

WORKDIR /dockerTutorial

COPY . .

ENV PORT=3000

RUN npm install

EXPOSE $PORT

ENTRYPOINT ["node", "app.js"]

Using the ENV command followed by the variable name and value assignment, we can add a new environmental variable to our dockerfile. Did you notice how the EXPOSE 3000 command has been changed to not explicitly mention the port number? Instead, it refers to the created PORT variable to get the exact port number. With this approach, if we have to change the port number, we only have to change one place in our code, which makes our application easy to maintain.

Now we have changed the dockerfile, next step is changing the app.js to refer to the created environment variable. For this, we replace the port number 3000 used inside the listen method with process.env.PORT.

const express = require('express')
const app = express()

app.get('/', (req, res) => {
    res.send("Hello World!")
})

app.listen(process.env.PORT, () => {
    console.log("Node server has started running")
})

Since we made changes to our application files and dockerfile, we have to build a new image for a new container. But first, we have to stop the currently running Docker container to achieve this.

We can use the docker stop command to stop the container.

docker stop f10

The value, f10, used in this command is the first three digits of the container’s ID.

We can use the command, docker kill, to stop the running container.

docker kill f10

The difference between docker kill and docker stop is that docker stop stops the container more gracefully by releasing using resources and saving the state. docker kill, however, stops the container more abruptly without properly releasing resources or saving the state. For a container running in a production environment, using docker stop to stop the container is the better choice.

After stopping a running container, make sure to clean the residual left by the container from the host environment using the following command.

Running the Container in Daemon Mode

When you try to run the above commands to stop the container, you would notice that the terminal tab we used to create the container cannot be used to run any more commands unless we kill the container. We can find a workaround for this by using a separate tab for running new commands.

But there is a better approach. We can run the container in the daemon mode. With the daemon mode, the container runs in the background without using the current tab to show outputs.

To start a container in the daemon mode, you simply have to add an additional -d flag to the docker run command.

docker run -d -p 8080:3000 docker-tutorial

Running the Container in Interactive Mode

To run a container in the interactive mode, the container should already be running. Once in the interactive mode, you can run commands to add or remove files to the container, list files, or run other bash commands we usually use.

Use the following command to run the container in the interactive mode.

docker exec -it e37 bash

Here, e37 is the container ID. Play around with the interactive mode using bash commands.

Conclusion

In the first tutorial of our Docker tutorial series, you learned how to create a Docker container for a simple Node.js application. But there is more you could do with Docker and containers. In our upcoming tutorials, we will see how to work with databases, volumes, and work with multiple containers used by an application built with microservices.