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
We are going to start by creating a file called Dockerfile
in the root of our repository.
touch Dockerfile
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
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
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 ./
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
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 . .
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
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
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"]
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"]
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
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
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
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
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
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
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.
- Login to your docker hub through docker CLI
docker login
- 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
- Push the image
# docker push new-repo:tagname
docker push <your-docker-username>/guessthebook
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
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)