Now that we have our setup docker setup ready we are good to start creating an image of our own.
The code for our basic node app is available in node-code directory.
We'll start with a simple node app single-app that'll have only one endpoint to greet.
The endpoint is http://*/greet/:name
dockerfile is the blueprint to create an image. It contains a set of instructions to tell docker about how to process our app. This includes :
Some common instructions we need to pass
We have our dockerfile for this example here
# Use the official Node.js image (version 20)
FROM node:20
# Set the working directory in the container
WORKDIR /app/single-app
# Copy only the package.json files to install dependencies
COPY package*.json /app/single-app
# Install dependencies
RUN npm install
# Copy the entire app code from node-apps to the working directory
COPY . /app/node-code/single-app
# Environment variable to pass port number,default 3000
ENV PORT=3000
# Expose the port that your app will run on
EXPOSE $PORT
# Command to run the application
CMD ["node", "/app/node-code/single-app/index.js"]
Notice that all paths are relative to the dockerfile location.
This is the file responsible to create our images, which we'll later use to create our containers.
Now that we have our dockerfile ready, we have told docker about the entire infrastructure required by our app to run, we can now create our image.
Get inside the ubuntu terminal and navigate to the folder where the dockerfile resides. we can do that adding /mnt/
to path and not using
:
with drive.
For example in my case it's cd /mnt/d/docker-demo/node-code/single-app
Use the command:
docker build -t <image_name>:<tag> <path to directory containing docker file>
-t
flag allows us to add tag explicitly to the image, otherwise "latest" will be used by default
I'll use docker build -t single-app:1.0 .
This will create the image for us. We can check with docker images
and our image should be in the list.
Now that we have our image handy, we are good to start with our container. This will be the actual functional unit where our app will run.
In the location let's start our container
docker run --name <container_name> -p <host_port>:<container_port> -e PORT=<container_port> <image_name>:<image_tag>
Here is the break down
I run docker run --name single-app -p 3200:3100 -e PORT=3100 single-app:1.0
This starts our app container and keeps the logs attached. A -d
flag can be attached to run container in detached mode and terminal won't be blocked.
Don't worry if your terminal is attached to the container, closing the terminal will not stop the container.
Now let's begin now. Try to access http://localhost:3200/greet/danish
docker ps
: This will display a list of all the currently. Our single-app
should be available here.docker logs <container-name>
: This will print the entire log of the container. In our case it'll be docker logs single-app
.
-f
.
docker logs -f single-app
and this will get us logs like we get on our ide/terminaldocker stop <container-name>
: This will stop the container. Imagine you run a bakery shop and close at 11. You can simply shutdown the service to take order and display on UI, not accepting live orders.
docker stop single-app
. Now let's verify if it has actually stopped. Run docker ps
and our single-app
is no where to be found.docker ps -a
: This displays the list of all containers available on our machine, wether it's running or not. Run this command and and we'll have our single-app
in the list with STATUS=Exited
docker start <container-name>
: Next day you're back on the shop and start accepting orders. You can start the service back up.
docker start single-app
: This will start our app again.
docker ps
. single-app
should have STATUS=Up
.docker logs single-app
: The logs will show server startup logs/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
actionGreet = (req, res) => {
const name = req.params.name;
logger.info(`Request received to greet ${name}`);
// used in v1
// res.send(`Hello ${name}, Welcome to your single-app container!`);
// used in v2
res.send(`Hello ${name}, Welcome to the single-app container!`);
};
docker rmi <image_name>:<tag>
: It removes the image from our local. For us docker rmi single-app:1.0
but we'll get an error
Error response from daemon: conflict: unable to remove repository reference "single-app:1.0" (must force) - container <container_id> is using its referenced image <image_id>
docker rm <container-name>
: It deletes the container.
docker rm single-app
is the way to go for us. But when we execute this, we'll get an error.
Error response from daemon: cannot remove container "/single-app": container is running: stop the container before removing or force remove
docker stop single-app
Now we can get rid of the container and thereafter the image.docker stop <container-name>
docker rm <container-name>
docker rmi <image-name>
docker build -t single-app:1.1 .
docker run --name single-app -p 3200:3100 -e PORT=3100 single-app:1.1
Now that we have the functional app ready, let's share it with the team and make it available for the QA to test.
Consider it as pushing our commits to remote, so that it can be available to everyone.
docker tag <image-name-local>:<image-tag-on-local> <dockerhub-username>/<repo>:<image-tag-on-remote>
docker tag single-app:1.1 devdanishjaved/docker-demo:single-app-1.1
docker push <dockerhub-username>/<repo>:<image-tag>
docker push devdanishjaved/docker-demo:single-app-1.1
Now let's image, the company was extremely pleased with our work and they have now given us a huge bonus and a paid vacation (Don't get carried away, i said imagine, its just our imagination 😂😂).
While we were on vacation, our teammate updated the app. Now we need the updated image. Similar to how we take a pull for the codebase.
docker pull <dockerhub-username>/<repo>:<image-tag>
Here we are to pull the v1.2 image, so for me its docker pull devdanishjaved/docker-demo:single-app-1.2
.
Upon successful pull, the logs would end something like this
Digest: sha256:<hash_of_image's_content>
Status: Downloaded newer image for devdanishjaved/docker-demo:single-app-1.2
docker.io/devdanishjaved/docker-demo:single-app-1.2
Use docker images
to check the image is available
Let's run the container and test.
I'll use the same command used previously to run the container just with the updated tag name.
docker run --name single-app-1.2 -p 3200:3100 -e PORT=3100 devdanishjaved/docker-demo:sing le-app-1.2
But got an error
docker: Error response from daemon: driver failed programming external connectivity on endpoint single-app-1.2 (a85b68c89fcea9db5044f3cb9737e89c730a105def35272a605686c8d31449f3): Bind for 0.0.0.0:3200 failed: port is already allocated.
It is because the VM's port 3100 is already occupied by an older version of the app. Although we got an error, but that was port forwarding step, so our container is ready. We can use docker ps
to check. Status would be Created, not Up.
Let's remove the app and recrate it with updated port.
Since the container never started we can remove it in one go with docker rm <container_id>
Now let's create the new container with docker run --name container_name_of_our_choice -p VM's_Port:Host_Port PORT=VM's_Port <dockerhub-username>/<repo>:<tag>
For me its docker run --name single-app-1.2 -p 3300:3300 -e PORT=3300 devdanishjaved/docker-demo:single-app-1.2
. We'll get our server stat-up logs and be able access the new changes.