Knowing how to use containers in application development is a must for a modern-day developer. One reason for the recent demand for containers has been the emergence of Docker. Docker has not only increased the use of containers, it has had a great impact on the way we approach application development.
If you are a developer who is yet to find a proper introduction to this popular technology, you are in the right place. In this article, we are going to introduce you to the concept of Docker and get a hands-on approach to learning Docker by dockerizing a simple application.
First, let’s clarify what Docker is and why it has become this important.
What is Docker?
Docker is a tool developers use to create, deploy, and run applications in an isolated environment through containers.
Here it is again, containers. Though this term is used a few times since the beginning of the article, you may not have an idea what a container is. In order to fully understand the above statement, we have to first understand what a container is.
What is a Container and Why Do We Need It?
A container is a software unit that packs application code and all the dependencies used in the application into a single package. Packaging allows the container to isolate the application from the host environment it is running in. The application sees the container as its environment instead of the host device. This abstraction guarantees that an application running in a development environment is able to run in a production environment without going through significant changes.
Even if several applications are running on the host device, the container isolates the containerized application from interfering with the operation of other applications and sharing their resources.
Before containers, virtual machines were used to isolate applications from the host environment. In virtual machines, each machine uses a separate operating system to run the application. Though this approach also achieves the purpose of isolating the applications, it has a downside of adding too much overhead on top of the application. Containers, on the other hand, share the OS kernel of the host device instead of using an OS of its own, which removes the overhead added by the virtual machines. This makes containers more lightweight and resource-efficient compared to virtual machines.
Though containers have been in use long before Docker, it’s safe to say that Docker is the biggest reason for the extreme popularity of containers in the modern programming world. Other than being open-source, Docker’s ease of use, reliability, and efficiency made the programming world instantly fall in love with this technology.
What are the Dockerfile, Docker Image, and Docker Engine?
Docker comes with its special language. Dockerfile, Docker image, and Docker engine are 3 words commonly used among Docker users. These are also the 3 most important components used when building Docker containers.
Dockerfile
Dockerfile contains a set of instructions to build a Docker image, which we are going to discuss next. These instructions will be run one after the other when creating the Docker image. Instructions in the Dockerfile contain information such as the host device’s OS, the programming language of the application, application directory location, network ports, and environment variables.
Docker Image
Docker image is a template that is used to create the final Docker container for the application. We can generate the Docker image of an application by running the docker build command with the Dockerfile as a parameter. To create the Docker container, we use the docker run command and the Docker image.
Docker Engine
Docker engine is where all the Docker containers are running on. Both Windows and Linux based applications can run on Docker engines.
How to Dockerize a Simple Application
Now we have got to the most interesting part of this tutorial. We are going to dockerize a simple application to get hands-on Docker experience. First, we will create a simple Node.js application and then, create the Dockerfile, Docker image, and finally the Docker container for the application.
Before continuing, however, make sure you have Docker installed on your device. You can follow the official documentation to install Docker on your Windows or Ubuntu OS. Check out the docs for other OS.
Create a Simple Node.js Application
We are going to create a simple Node application that sends a “Hello World” message when we visit the root route.
Follow these steps to set up your application:
npm init
npm install express --save
Inside the directory, app.js
file contains our main application code.
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")
})
Create the Dockerfile for the Application
Now we can create the Dockerfile with the information that is needed to create the Docker image.
Create a file named Dockerfile
inside the application directory. To create the Docker Image for our application, the Dockerfile should contain a set of commands like this.
FROM node:latest
WORKDIR /docker-tutorial
COPY . .
ENV PORT 3000
RUN npm install
EXPOSE $PORT
ENTRYPOINT ["node", "app.js"]
Now we will go through what each of these commands means.
- FROM—This command sets the base image and the new image of the application is built on top of this. In our case, we use an image that contains npm and the latest Node.js version. This image is pulled from Docker Hub which is a public repository of Docker images.
- WORKDIR—This command sets the working directory for the application that will be running inside the container.
- COPY—This command copies the files in the application directory to the working directory which we set with the previous command. You can pass the path to a specific file name or do as above to copy all the files in the application directory to the Docker image. In the latter case, make sure you have navigated to the application directory on the command line when running the docker build command.
- ENV—In the Node application, note how we passed the environment variable, PORT (process.env.PORT), to the app.listen function instead of directly passing the port number the application should listen to. Therefore, we have to set the PORT environment variable in the application environment. For our application that is going to the Docker container. So, we use the ENV command to pass the variables that we want to set as environment variables inside the Docker container.
- RUN—This command runs npm install to install the dependencies used in our application, which are saved to package.json file.
- EXPOSE—This command exposes the application to listen to the given port. Since we have already set the port number as an environment variable, we pass the variable name, $PORT, in place of the actual port number. However, remember that the application is exposed to the port 3000 inside the container’s environment and not the host device’s environment.
- ENTRYPOINT—This command sets how to enter, or how to start, our application. Docker joins the array we pass to create a single command that starts the application, which is node app.js.
Build the Docker Image
We use docker build command to build the Docker image from the Dockerfile. Here is how it works:
docker build -t <image-name> <dockerfile-location>
Make sure you have navigated to the application directory on your command-line before running the command. You can pass a dot (.) in place of the Dockerfile location to indicate that the Dockerfile is in the current directory.
For our example we will run:
docker build -t docker-tutorial .
Output:
Sending build context to Docker daemon 2.008MB
Step 1/7 : FROM node:latest
latest: Pulling from library/node
81fc19181915: Pull complete
ee49ee6a23d1: Pull complete
828510924538: Pull complete
a8f58c4fcca0: Pull complete
33699d7df21e: Pull complete
923705ffa8f8: Pull complete
ae06f9217656: Pull complete
39c7f0f9ab3c: Pull complete
df076510734b: Pull complete
Digest: sha256:719d5524c7e927c2c3e49338c7dde7fe56cb5fdb3566cdaba5b37cc05ddf15da
Status: Downloaded newer image for node:latest
---> dcda6cd5e439
Step 2/7 : WORKDIR /docker-tutorial
---> Running in 8797780960e9
Removing intermediate container 8797780960e9
---> b80abb69066b
Step 3/7 : COPY . .
---> cc9215d75956
Step 4/7 : ENV PORT 3000
---> Running in 4bf08e16b94d
Removing intermediate container 4bf08e16b94d
---> 95007721d5ee
Step 5/7 : RUN npm install
---> Running in d09f45f0bbd7
npm WARN docker-tutorial@1.0.0 No description
npm WARN docker-tutorial@1.0.0 No repository field.
audited 50 packages in 1.146s
found 0 vulnerabilities
Removing intermediate container d09f45f0bbd7
---> 292a854f73e2
Step 6/7 : EXPOSE $PORT
---> Running in f2ae755655b3
Removing intermediate container f2ae755655b3
---> 6d42325fe934
Step 7/7 : ENTRYPOINT ["node", "app.js"]
---> Running in d657168980d8
Removing intermediate container d657168980d8
---> 0c6df3f042eb
Successfully built 0c6df3f042eb
Successfully tagged docker-tutorial:latest
Once you run the docker build command, Docker will execute each command in the Dockerfile consecutively. When executing the FROM command, if the Node image hasn’t been pulled to your device before, Docker will pull the image from Docker Hub.
Once all the commands are executed, you will see the message “successfully built” if the image was created without running into an error.
You can use the command, docker images
, to see all the images currently in your device.
Output:
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-tutorial latest 0c6df3f042eb 3 minutes ago 943MB
node latest dcda6cd5e439 2 weeks ago 942MB
Create the Docker Container
We use the following command to create and run the Docker container using the already built Docker image.
docker run -d -p 8080:3000 docker-tutorial
Output:
a7cc6a41996fa565890b57e292aff5df99cb5e13a8c7ab8cea2506e16d70d01e
Here, -d
flag is used to run the container in the daemon mode. The -p flag is used to map the port the container opens to the outside world to the one that the application is listening to. In the Dockerfile, previously, we set the internal port, or the application port, to 3000. So, here, we are mapping the port 8080, which the container opens to the host device, to port 3000. docker-tutorial
is the name of the Docker image.
You can see the currently running Docker containers on your device with their IDs using the docker ps
command.
With that, we have completed the process of dockerizing our simple Node application. You can visit the URL http://localhost:8080 on your browser to check if the application and the container are working properly. If you have succeeded, you will see the message, “Hello World”. (If your Docker engine is not running in localhost, replace localhost with the IP address it is using on your device.)
Stopping and Removing a Docker Container
If you want to stop a running Docker container use the following command.
docker stop <first-3-characters-of-the-container-id>
However, this only stops the container without entirely removing it from the system. If you want to restart the container, you can use the docker start command with the container ID.
If you want to completely remove a stopped container, use this command.
docker rm <first-3-characters-of-the-container-id>
Summary
Today, Docker has become a technology too large to ignore for every developer out there. Our article introduced you to this popular technology with an explanation of what Docker is and working with Docker to containerize an application. We learned how to create a Dockerfile, Docker image, and finally, a Docker container for a simple Node.js application. But this is just the beginning of Docker. It has a lot more capabilities and advanced configurations to help you create, run, and deploy apps rather easily and efficiently. So, take this introduction to Docker as an opportunity to dive deeper and explore more complex concepts related to Docker.
Thanks for reading!
If you like the story, please don't forget to subscribe to our free newsletter so we can stay connected: https://livecodestream.dev/subscribe
Top comments (0)