Table of contents
DockerFile
A Dockerfile is a simple text file that contains instructions on how to build your images. These instructions are executed successively to perform actions on the base image to create a new container.
Create a Dockerfile for a simple web application (e.g. a Node.js or Python app)
Let's create a folder named
node-todo-cicd
which will contain all the code needed for the app. Let's assume we have a file namedapp.js
which has the code for creating app.
const express = require('express'),
bodyParser = require('body-parser'),
// In order to use PUT HTTP verb to edit item
methodOverride = require('method-override'),
// Mitigate XSS using sanitizer
sanitizer = require('sanitizer'),
app = express(),
port = 8000
app.use(bodyParser.urlencoded({
extended: false
}));
// https: //github.com/expressjs/method-override#custom-logic
app.use(methodOverride(function (req, res) {
if (req.body && typeof req.body === 'object' && '_method' in req.body) {
// look in urlencoded POST bodies and delete it
let method = req.body._method;
delete req.body._method;
return method
}
}));
let todolist = [];
/* The to do list and the form are displayed */
app.get('/todo', function (req, res) {
res.render('todo.ejs', {
todolist,
clickHandler: "func1();"
});
})
/* Adding an item to the to do list */
.post('/todo/add/', function (req, res) {
// Escapes HTML special characters in attribute values as HTML entities
let newTodo = sanitizer.escape(req.body.newtodo);
if (req.body.newtodo != '') {
todolist.push(newTodo);
}
res.redirect('/todo');
})
/* Deletes an item from the to do list */
.get('/todo/delete/:id', function (req, res) {
if (req.params.id != '') {
todolist.splice(req.params.id, 1);
}
res.redirect('/todo');
})
// Get a single todo item and render edit page
.get('/todo/:id', function (req, res) {
let todoIdx = req.params.id;
let todo = todolist[todoIdx];
if (todo) {
res.render('edititem.ejs', {
todoIdx,
todo,
clickHandler: "func1();"
});
} else {
res.redirect('/todo');
}
})
// Edit item in the todo list
.put('/todo/edit/:id', function (req, res) {
let todoIdx = req.params.id;
// Escapes HTML special characters in attribute values as HTML entities
let editTodo = sanitizer.escape(req.body.editTodo);
if (todoIdx != '' && editTodo != '') {
todolist[todoIdx] = editTodo;
}
res.redirect('/todo');
})
/* Redirects to the to do list if the page requested is not found */
.use(function (req, res, next) {
res.redirect('/todo');
})
.listen(port, function () {
// Logging to console
console.log(`Todolist running on http://0.0.0.0:${port}`)
});
// Export app
module.exports = app;
Now, we will create a Dockerfile. Let's open the vim editor using vim Dockerfile
and start writing our Dockerfile.
Dockerfile -vim Dockerfile
#Specify the base image
FROM node:12.2.0-alpine
#Specify the working directory
WORKDIR app
#Copy the code into the current directory
COPY . .
#Run the application
RUN npm install
RUN npm run test
#Expose the port
EXPOSE 8000
#CMD command is used to execute all the above code once the container is created
CMD ["node","app.js"]
Build the image using the Dockerfile and run the container.
Once the Dockerfile is created, we will build an image from the Docker file using
docker build . -t <image-name>
ubuntu@ip-172-31-19-129:~/dockerProjects/node-todo-cicd$ docker build . -t node-todo
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/
Sending build context to Docker daemon 441.9kB
Step 1/7 : FROM node:12.2.0-alpine
12.2.0-alpine: Pulling from library/node
e7c96db7181b: Pull complete
a9b145f64bbe: Pull complete
3bcb5e14be53: Pull complete
Digest: sha256:2ab3d9a1bac67c9b4202b774664adaa94d2f1e426d8d28e07bf8979df61c8694
Status: Downloaded newer image for node:12.2.0-alpine
---> f391dabf9dce
Step 2/7 : WORKDIR app
---> Running in b5c7b6471816
Removing intermediate container b5c7b6471816
---> 7c5566159968
Step 3/7 : COPY . .
---> b34025b6395d
Step 4/7 : RUN npm install
---> Running in b6ebe78fb38e
npm WARN read-shrinkwrap This version of npm is compatible with lockfileVersion@1, but package-lock.json was generated for lockfileVersion@2. I'll try to do my best with it!
> ejs@2.7.4 postinstall /app/node_modules/ejs
> node ./postinstall.js
Thank you for installing EJS: built with the Jake JavaScript build tool (https://jakejs.com/)
npm WARN my-todolist@0.1.0 No repository field.
npm WARN my-todolist@0.1.0 No license field.
added 291 packages from 653 contributors and audited 291 packages in 8.157s
found 15 vulnerabilities (6 moderate, 6 high, 3 critical)
run `npm audit fix` to fix them, or `npm audit` for details
Removing intermediate container b6ebe78fb38e
---> 284036a251df
Step 5/7 : RUN npm run test
---> Running in c34fcc5b47ac
> my-todolist@0.1.0 test /app
> mocha --recursive --exit
Simple Calculations
This part executes once before all tests
Test1
executes before every test
✓ Is returning 5 when adding 2 + 3
executes before every test
✓ Is returning 6 when multiplying 2 * 3
Test2
executes before every test
✓ Is returning 4 when adding 2 + 3
executes before every test
✓ Is returning 8 when multiplying 2 * 4
This part executes once after all tests
4 passing (13ms)
Removing intermediate container c34fcc5b47ac
---> 5cba505a8ec3
Step 6/7 : EXPOSE 8000
---> Running in acc80a826ff7
Removing intermediate container acc80a826ff7
---> 685e9bc4b795
Step 7/7 : CMD ["node","app.js"]
---> Running in 56edff7f4c63
Removing intermediate container 56edff7f4c63
---> 74e7d6f32ed1
Successfully built 74e7d6f32ed1
Successfully tagged node-todo:latest
We can see that the docker image was created and tagged successfully as node-todo:latest
. Now run the container using docker run -d -p <port> <image-name>
. -d is used to detach the container from the system and then run and -p is used to publish the port. As we have used 8000 port for the javascript app, the final command would be - docker run -d -p 8000:8000 node-todo:latest
We can check the running containers using docker ps
command where we can see the container id, the image, the port on which the container is running, its status and many more details as below-
ubuntu@ip-172-31-19-129:~/dockerProjects/node-todo-cicd$ docker run -d -p 8000:8000 node-todo:latest
280174436fb97506afb346cb2303ec7345701a8077d03820065981679476147f
ubuntu@ip-172-31-19-129:~/dockerProjects/node-todo-cicd$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
280174436fb9 node-todo:latest "node app.js" 3 seconds ago Up 2 seconds 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp sweet_feynman
Verify that the application is working as expected by accessing it in a web browser
Firstly, we need to add inbound rules to Port 8000 so that the image can be accessed from anywhere. To do this, we will click on our instance id, go to Security and then Security Groups. To add inbound rules, click on Edit Inbound -> Add rule -> enter the Port Number in Port Range field and save the changes. Now go back to the instance id, copy the IPV4 Public address and open it in the browser by adding ":8000" to the IPV4 address. You can verify whether the application is accessible in a web browser or not by hitting the final url.
We can see that the application is running successfully on port 8000 as above.
Push the image to a public or private repository (e.g. Docker Hub )
Follow the below steps to push the container Image to the Docker repository:
i) First, create an account on https://hub.docker.com/ and login. You can check that the Repository is empty on the newly created account.
ii) Now go back to your terminal and type docker login to get access to Docker Hub.
ubuntu@ip-172-31-92-1:~/dockerProjects/python-app$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: shilpidns
Password:
WARNING! Your password will be stored unencrypted in /home/ubuntu/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
iii) Let's select an image that we want to push using docker images.
ubuntu@ip-172-31-92-1:~/dockerProjects/python-app$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-app latest 6c895f06a1dc About an hour ago 1.01GB
python 3.9 d2ed990198a7 3 weeks ago 997MB
iv) Before pushing the docker image to DockerHub, we need to create a tag using the username of DockerHub alongwith the image name. Check the syntax using docker image tag.
We need to create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
ubuntu@ip-172-31-92-1:~/dockerProjects/python-app$ docker image tag
"docker image tag" requires exactly 2 arguments.
See 'docker image tag --help'.
Usage: docker image tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
v) Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE using the above syntax. We can see a new image is created with the updated Repository name.
ubuntu@ip-172-31-19-129:~$ docker image tag node-todo:latest shilpidns/node-todo:latest
ubuntu@ip-172-31-19-129:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
node-todo latest 74e7d6f32ed1 21 minutes ago 104MB
shilpidns/node-todo latest 74e7d6f32ed1 21 minutes ago 104MB
vi) Now, push the code to DockerHub using docker push <updated-image-name>
ubuntu@ip-172-31-19-129:~$ docker push shilpidns/node-todo:latest
The push refers to repository [docker.io/shilpidns/node-todo]
96beed8185a6: Pushed
a9354d181717: Pushed
ed9767635c26: Pushed
bcee6d50db3c: Pushed
917da41f96aa: Mounted from library/node
7d6e2801765d: Mounted from library/node
f1b5933fe4b5: Mounted from library/node
latest: digest: sha256:e6513e7f98d75db1ad76b4b650cbe2bf845ee5b4ad66ae02a33e3bdfdf2601b1 size: 1785
vii) The code is successfully pushed to DockerHub. You can refresh the page and find your repository present on the Repository page.
Hurray! The code is successfully pushed to the Docker Hub.
Hope you liked the article. Keep reading and exploring!
Thank You!
~Shilpi