DEV Community

Cover image for How to containerize your web app- a beginner-friendly tutorial for Dockerfile
Swikriti Tripathi
Swikriti Tripathi

Posted on

How to containerize your web app- a beginner-friendly tutorial for Dockerfile

Welcome to part 2 of the series Docker for Dummies in this blog we are going to create an image of a small web app and learn about what each step does. Without any further ado let's get started. For this blog, I'm going to use a simple vue-app quiz app that'll let you guess the name of the book based on the first line. If you want to follow along with the setup you can find the GitHub link to the web app here or you can use your app or you can create a simple hello-world app in node or framework/language of your choosing.

Let's create the image

Note: this tutorial follows ubuntu commands but they should be similar for other OS's as well

Let's clone the app and go inside of it

git clone https://github.com/SwikritiT/Guessthebook-blog
cd Guessthebook-blog

Enter fullscreen mode Exit fullscreen mode

We are going to start by creating a file called Dockerfile in the root of our repository.

touch Dockerfile
Enter fullscreen mode Exit fullscreen mode

Dockerfile

A Dockerfile is a script that contains all the steps necessary to build an image. Dockerfile starts with something called base image. A base image is a pre-configured environment that our images build upon. The base image can be an OS like Linux, alpine, or some application stack. Choosing an appropriate base image is crucial for the overall size and productivity of the image of our application. We will talk more about how to select the appropriate base image in the later part of this series so for now let's keep in mind that a lighter base-image will create a lighter app image(this comes with its limitations which we will talk about in more detail in the later part of this series). So, for this app, we are going to use node:alpine images as a base, you can select your needed version of a base image from an image registry like Docker Hub.

Note: If you're not building the image of the node application, you need to select the image appropriate for your stack

# Start with a base image
FROM node:alpine
Enter fullscreen mode Exit fullscreen mode

FROM: Specifies the base image to use. Every Dockerfile starts with this instruction.

The next step is to set the WORKDIR i.e. working directory for our container where commands will be executed and files will be stored by default. Normally, for web apps, workdir is set to /usr/src/app but you can customize it as per your need.

# Set the working directory
WORKDIR /usr/src/app
Enter fullscreen mode Exit fullscreen mode

Now let's copy the necessary config files to our container, in our case, package.json and lockfile. For this, we will use COPY command

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./
Enter fullscreen mode Exit fullscreen mode

COPY or ADD: Copies files from your local filesystem into the container.

The next step is to install dependencies just like we do in our host machine and for this RUN command can be used

# Install the application dependencies
RUN npm install
Enter fullscreen mode Exit fullscreen mode

RUN: Executes commands in the container. These commands are typically used to install software packages.

Now we copy the rest of the files to our container's working directory

# Copy the current directory contents into the container at /usr/src/app
COPY . .
Enter fullscreen mode Exit fullscreen mode

The above command will copy every file and folder that is in your app to the container's working directory. This includes the build file, files created by IDE, or other miscellaneous files/folders that might not be necessary for building the image. So, it is a good practice to either copy only necessary files or create a .dockerignore file with the list of files and folders that you don't want to be copied inside the container. The syntax of the .dockerignore file is similar to that of .gitignore. Learn more here

The next step is to build our application

# Build the application
RUN npm run build
Enter fullscreen mode Exit fullscreen mode

The next step is to Expose the ports on which a containerized application listens for network connections

# Make port 3000 available to the world outside the container
EXPOSE 3000
Enter fullscreen mode Exit fullscreen mode

EXPOSE is a way to document which ports the application running inside the container will use. It does not map the port to the host machine’s ports. It simply indicates which ports are intended to be accessible.

We've come to the final stage where we will run the application. There are two commands that we can use to do this CMD and ENTRYPOINT for this part we will be using CMD and we'll talk about ENTRYPOINT in later parts.

Since we will be running the application in production env we will use the preview command for this. Later we'll also create a dockerized dev env.

# Define the command to run the app
CMD ["npm","run","preview"]
Enter fullscreen mode Exit fullscreen mode

CMD: Provides the command that will be run when a container is started from the image. Only one CMD instruction can be present in a Dockerfile.

Now let's look at the whole Dockerfile

# Start with a base image
FROM node:alpine

# Set the working directory
WORKDIR /usr/src/app

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install the application dependencies
RUN npm install

# Copy the current directory contents into the container at /usr/src/app
COPY . .

# Build the application
RUN npm run build

# Make port 3000 available to the world outside the container
EXPOSE 3000

# Define the command to run the app
CMD ["npm","run","preview"]
Enter fullscreen mode Exit fullscreen mode

In Docker, each line in your Dockerfile creates a new layer in the final image, like adding ingredients to a sandwich. These layers stack on top of each other, with each layer representing a change or addition, such as copying files or installing software. Docker saves these layers, and if you rebuild your image and some layers haven’t changed, Docker reuses them, speeding up the build process and reducing redundancy. So, in the above dockerfile, we have 8 layers.

Build the image

Now that we've created the image it's time to build it and get it running. We can run the following command in the terminal from the root of our repository to build a docker image

$ docker build . -t guessthebook:v1

[+] Building 1.1s (11/11) FINISHED                               docker:default
 => [internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 191B                                       0.0s
 => [internal] load metadata for docker.io/library/node:20-alpine          0.9s
 => [internal] load .dockerignore                                          0.0s
 => => transferring context: 129B                                          0.0s
 => [1/6] FROM docker.io/library/node:20-alpine@sha256:66c7d989b6dabba6b4  0.0s
 => [internal] load build context                                          0.0s
 => => transferring context: 1.41kB                                        0.0s
 => CACHED [2/6] WORKDIR /usr/src/app                                      0.0s
 => CACHED [3/6] COPY package*.json ./                                     0.0s
 => CACHED [4/6] RUN npm install                                           0.0s
 => CACHED [5/6] COPY . .                                                  0.0s
 => CACHED [6/6] RUN npm run build                                         0.0s
 => exporting to image                                                     0.0s
 => => exporting layers                                                    0.0s
 => => writing image sha256:08f32d7f583b7e65a844accafff8fc19930849204c5ae  0.0s
 => => naming to docker.io/library/guessthebook:v1                         0.0s

Enter fullscreen mode Exit fullscreen mode

You will get the output like such with the information of each layer being built. The command docker build . -t guessthebook:v1 reads the Dockerfile in the current directory, builds a Docker image according to its instructions, and tags this image as guessthebook with version v1.

Now if you run the following command in your terminal, you should see the relevant info about the image that we just created

docker images

REPOSITORY                                   TAG            IMAGE ID       CREATED         SIZE
guessthebook                                 v1             08f32d7f583b   5 minutes ago   275MB
Enter fullscreen mode Exit fullscreen mode

Running a Docker Container

Once you have built the image, you can run it with the docker run command:

docker run -p 3000:3000 guessthebook:v1
Enter fullscreen mode Exit fullscreen mode

This command runs a container from the guessthebook:v1 image, mapping port 3000 of the container to port 3000 on the host machine. We use -p for port mapping.

We can improvise the above command to use the option -d to run it in detached mode

 docker run  -d -p 3000:3000 guessthebook:v1
Enter fullscreen mode Exit fullscreen mode

After running the docker container you can visit http://localhost:3000 to ensure everything works fine. If everything is setup and working correctly you should be greeted with this screen

app homepage

You can now take a rest and play the quiz to see how many you get right.

Stop container

You can stop the container with the following command

docker stop <container_name or id> # can run `docker ps` to get the name and id
Enter fullscreen mode Exit fullscreen mode

Publish the image to the docker hub

If we want to take this a step further we can publish the image to the docker hub. For that create an account on Docker Hub if you haven't already. Next, you can create a new repository.

docker hub create repo page

  1. Login to your docker hub through docker CLI
docker login
Enter fullscreen mode Exit fullscreen mode
  1. Tag your local image to match the repo image
# docker tag local-image:tagname new-repo:tagname
docker tag guessthebook:v1 <your-docker-username>/guessthebook
Enter fullscreen mode Exit fullscreen mode
  1. Push the image
# docker push new-repo:tagname
docker push <your-docker-username>/guessthebook
Enter fullscreen mode Exit fullscreen mode

Now, you can go and check if your docker hub has the image that you just pushed.

To test the image you can now run the image by pulling directly from Docker Hub

# let's remove the locally tagged image first
docker rmi <your-docker-username>/guessthebook

# run the container
docker run -p 3000:3000 <your-docker-username>/guessthebook
Enter fullscreen mode Exit fullscreen mode

That's it for this blog. Hope you enjoyed this one and learned something new as well! See you in the next part of this series. If you have any queries or suggestions please comment them below!

Top comments (0)