{"id":2355,"date":"2018-08-01T07:00:50","date_gmt":"2018-08-01T07:00:50","guid":{"rendered":"https:\/\/blog.ssdnodes.com\/blog\/?p=2355"},"modified":"2025-05-18T19:58:39","modified_gmt":"2025-05-18T19:58:39","slug":"docker-images-deploy","status":"publish","type":"post","link":"https:\/\/www.ssdnodes.com\/blog\/docker-images-deploy\/","title":{"rendered":"Docker images: How to containerize your project"},"content":{"rendered":"<div id=\"preview1\" class=\"g-b g-b--t1of2 split split-preview\">\n<div id=\"preview\" class=\"preview-html\">\n<p>Most people have installed Docker on their workstations or their VPS, but not many have explored its full potential. Maybe you\u2019ve used Docker to host your website or blog, or perhaps you use it on your local machine to <a href=\"https:\/\/www.ssdnodes.com\/blog\/docker-uses-minimalists\/\">play around<\/a> with applications others have written. Maybe working with Docker images seems like a stretch, and the extent of your Docker knowledge is the <code>docker pull<\/code>, <code>docker run<\/code> and <code>docker start<\/code> commands.<\/p>\n<p>If that\u2019s true, but you want to do more, you\u2019ve come to the right place! In this post, we\u2019ll learn how applications like an Apache web server or the Python runtime are containerized, and how you can create Docker images\/containers for your own projects as well.<\/p>\n<h2><a id=\"Prerequisites_4\"><\/a>Prerequisites<\/h2>\n<ul>\n<li>Docker running on your local computer<br \/>\nDocker installed on your VPS\u2014for information about how to install Docker, check out our <a href=\"https:\/\/www.ssdnodes.com\/blog\/tutorial-getting-started-with-docker-on-your-vps\/\">getting started with Docker tutorial<\/a>.<br \/>\nAn account wither Docker Hub or any other public\/private Docker registry<br \/>\nA fundamental knowledge about docker and a few standard commands<\/li>\n<\/ul>\n<h2>Docker images, containers, and layers<\/h2>\n<p>Let\u2019s begin with the various states of a Dockerized application. When we build a Docker image for our project, the image itself is just a blob of data (like a binary) which you can push to a Docker registry, pulled into a different computer, and then used to create identical containers.<\/p>\n<p>When you run a Docker container, the Docker engine manages it, gives it resources, includes it within the Docker filesystem, and allocates an IP address to it. The container is the image manifesting itself as a running application, and the Docker engine allows you to start a container, stop it, or resume it\u2014exactly like when you run a virtual machine.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"571\" height=\"571\" class=\"aligncenter size-full wp-image-2359\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/07\/201808_docker-images-example.jpg\" alt=\"Docker images: How they work\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/07\/201808_docker-images-example.jpg 571w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/07\/201808_docker-images-example-300x300.jpg 300w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/07\/201808_docker-images-example-150x150.jpg 150w\" sizes=\"auto, (max-width: 571px) 100vw, 571px\" \/><\/p>\n<p><strong>Layers<\/strong> are an essential substructure of a Docker image. Without the concept of layers, creating a Docker image would involve creating <em>everything<\/em>, from C libraries to core dependencies like language support for your application itself, which could then depend on PHP, Node.js, etc.<\/p>\n<p>But, thanks to layers, we don\u2019t have to reinvent the wheel every time. We could use pre-existing images of, say, Ubuntu or Alpine Linux, and layer our application on top of it. This is the most common way of creating Docker images, and this is what we\u2019ll learn in this post.<\/p>\n<h2><a id=\"Containerizing_a_Nodejs_application_24\"><\/a>Containerizing a Node.js application<\/h2>\n<p>Let\u2019s try to create a container that runs a simple HTTP server written in Node. Create a folder in your local workstation <code>mkdir ~\/sampleapp<\/code>. Within this folder, let\u2019s create two text files: <code>app.js<\/code> and <code>Dockerfile<\/code>. The details of <code>app.js<\/code> file are not that important, but you can glean some of its workings via the comments included.<\/p>\n<pre><code>const http = require(\"http\");\n\nhttp.createServer(function (request, response) {\n   \/\/ Send the HTTP header\n   \/\/ HTTP Status: 200 : OK\n   \/\/ Content Type: text\/plain\n   response.writeHead(200, {'Content-Type': 'text\/plain'});\n\n   \/\/ Send the response body as \"Hello World\"\n   response.end('Hello, World!\\n');\n}).listen(80);\n\n<\/code><\/pre>\n<p>It is a simple HTTP server that listens on port 80 and can respond to incoming requests with the message <code>Hello, World!<\/code>. Let\u2019s turn our attention to the <code>Dockerfile.<\/code><\/p>\n<p>A <code>Dockerfile<\/code> is a specially named file which you place at the root of your project\u2019s repository. The command <code>docker build<\/code> goes through the instructions in this <code>Dockerfile<\/code> and crafts a Docker image with your application in it.<\/p>\n<p>Let\u2019s look at a simple <code>Dockerfile<\/code> which you should create in the same folder <code>sampleapp<\/code>:<\/p>\n<pre><code>FROM node:latest\nWORKDIR \/app\nCOPY . \/app\nEXPOSE 80\nCMD [\"node\",  \"app.js\"]\n<\/code><\/pre>\n<p>Before looking at the minutiae of Dockerfile, let\u2019s build an image first. Do this by running the following command in the folder where you placed your <code>Dockerfile<\/code> and <code>app.js<\/code> files:<\/p>\n<pre><code>$ docker build -t sampleapp .\n<\/code><\/pre>\n<p>This will look into the current directory (indicated by a period at the end) for a Dockerfile, execute its instructions, and build an image with tag <code>sampleapp<\/code>. Verify that it is created by running <code>docker images<\/code>.<\/p>\n<p>Let\u2019s create a container from this image by running the command:<\/p>\n<pre><code>$ docker run -p 8070:80 sampleapp\n<\/code><\/pre>\n<p>And you can now visit <a href=\"http:\/\/localhost:8070\" target=\"_blank\" rel=\"noopener\">http:\/\/localhost:8070<\/a> to see the <code>Hello, World!<\/code> message.<\/p>\n<p><strong><img loading=\"lazy\" decoding=\"async\" width=\"1000\" height=\"485\" class=\"aligncenter size-full wp-image-2358\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/07\/201808_docker-images-helloworld.jpg\" alt=\"Docker images: A 'Hello World!' Node.js app\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/07\/201808_docker-images-helloworld.jpg 1000w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/07\/201808_docker-images-helloworld-300x146.jpg 300w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/07\/201808_docker-images-helloworld-768x372.jpg 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/strong><\/p>\n<p>Now, let\u2019s go through the <code>Dockerfile<\/code> line by line to see how this was accomplished.<\/p>\n<h3><a id=\"FROM_imageversion_and_RUN_command_77\"><\/a>FROM image:version and RUN command<\/h3>\n<p>The first line <code>FROM node:latest<\/code> fetches the latest Node container release by Node Foundation on Docker Hub. If you need a specific version, say version 8, then you can change <code>latest<\/code> to the version number: <code>FROM node:8<\/code>.<\/p>\n<p>This <code>FROM<\/code> statement brings in the base image, and the subsequent commands can add extra layers on top of it. But this base image itself will have a Node environment set up for us to run .js files and use the <code>npm<\/code> package manager.<\/p>\n<p>Another way would be to bring in an Ubuntu base image and install Node on top of it.<\/p>\n<pre><code>FROM ubuntu:18.04\nRUN apt-get update -y &amp;&amp; apt-get install node -y\n<\/code><\/pre>\n<p>The FROM command brings in the official Ubuntu image. The <code>RUN<\/code> will execute any statement that follows it, inside of an intermediate container\u2019s shell. In this case, it will run <code>apt-get update<\/code> with a <code>-y<\/code> flag, because we can\u2019t give any inputs to the container, and will then continue towards installing Node.js.<\/p>\n<p>Once the installation is finished, the modifications made to this intermediate container becomes a part of the image as another layer gets added on top of the Ubuntu base image.<\/p>\n<p><em>As a rule of thumb we try to keep the number of layers to a minimum, for a faster build process, which is why we went directly with <code>node<\/code> as our base image.<\/em><\/p>\n<h3><a id=\"WORKDIR_app_96\"><\/a>WORKDIR \/app<\/h3>\n<p>Typically, your application lives in a specific directory. If you are running a web server, <code>\/var\/www\/html<\/code> is the common choice. Similarly, our application needs to live in a directory <strong>inside<\/strong> the container. To specify this, we use the <code>WORKDIR<\/code> keyword followed by the absolute path of the desired folder inside of the container. If the folder (or directory) doesn\u2019t exist, Docker will create it.<\/p>\n<pre><code>WORKDIR \/app\n<\/code><\/pre>\n<p>Now, when we run <code>CMD<\/code> later, to execute the <code>app.js<\/code> file within the container, the <code>Dockerfile<\/code> will first look into the <code>\/app<\/code> directory for a file called <code>app.js<\/code> instead of looking elsewhere. This way, you won\u2019t have to mention it explicitly as <code>node \/app\/index.js<\/code>.<\/p>\n<h3><a id=\"COPY__app_106\"><\/a>COPY . \/app<\/h3>\n<p>While we have created a working directory for our app, you haven\u2019t yet placed the app inside it. The <code>COPY . \/app<\/code> instruction would copy, to the container\u2019s <code>\/app<\/code> directory, the entire contents of the current directory (where your <code>Dockerfile<\/code> is), from your local machine. This is why we place the <code>Dockerfile<\/code> at the root of our project\u2019s repository, so it transfers every subfolder and every file within those folders into the container.<\/p>\n<p>You can create an additional file, called <code>.dockerignore<\/code>, and mention the folders and files within your repository that you wish to exclude from being containerized.<\/p>\n<h3><a id=\"EXPOSE_112\"><\/a>EXPOSE<\/h3>\n<p>The expose command exposes the port 80 of the image. Since our node app listens on port 80, this port needs to be exposed. We would later configure port forwarding so that the request would flow through a specific port on the host system to the port 80 of our container.<\/p>\n<pre><code>EXPOSE 80\n<\/code><\/pre>\n<h3><a id=\"CMD_node_index_120\"><\/a>CMD [\u201cnode\u201d, \u201cindex\u201d]<\/h3>\n<p>The <code>CMD<\/code> command is similar to <code>RUN<\/code> command. The array of strings that follow the keyword are executed as <code>node app<\/code> inside the container. The difference between <code>CMD<\/code> and <code>RUN<\/code> is that <code>RUN<\/code> executes the command and adds that becomes a part of the Docker image.<\/p>\n<p>Commands followed by <code>CMD<\/code> are executed when you create a container and the result of these commands live and die with that container, and are not made part of the Docker image. As mentioned earlier, our working directory is <code>\/app<\/code>, so the command executes the <code>node index<\/code> command inside of the <code>\/app<\/code> directory.<\/p>\n<h2><a id=\"Essentials_of_a_Dockerfile_126\"><\/a>Essentials of a Dockerfile<\/h2>\n<p>To summarize the gist of it:<\/p>\n<table class=\"table table-striped table-bordered\">\n<thead>\n<tr>\n<th>Keyword<\/th>\n<th>Effect<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>FROM<\/td>\n<td>Pulls a base image<\/td>\n<\/tr>\n<tr>\n<td>RUN<\/td>\n<td>Runs command to modify the image<\/td>\n<\/tr>\n<tr>\n<td>WORKDIR<\/td>\n<td>Creates a Working directory<\/td>\n<\/tr>\n<tr>\n<td>CMD<\/td>\n<td>Commands that run upon container creation<\/td>\n<\/tr>\n<tr>\n<td>EXPOSE<\/td>\n<td>Exposes a specific port number of a container<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>While there are other keywords and essential concepts related to Dockerfile, this is enough to get you started for the first time.<\/p>\n<h2><a id=\"The_Docker_Registry_140\"><\/a>The Docker Hub<\/h2>\n<p>Having created an image, let\u2019s use the Docker Hub to save and share this image with others.<\/p>\n<p>Sign up at <a href=\"https:\/\/hub.docker.com\/\" target=\"_blank\" rel=\"noopener\">Docker Hub<\/a> if you haven\u2019t already and create a repository named <code>sampleapp<\/code>.<\/p>\n<p>Login to your Docker Hub using the command:<\/p>\n<pre><code>$ docker login\n<\/code><\/pre>\n<p>Running this command will prompt you for your username and password. Once you\u2019re logged in, you might want to rebuild the Docker image (on your local machine) with the appropriate tag. Let\u2019s remove the one we created earlier.<\/p>\n<pre><code>$ docker rmi sampleapp\n<\/code><\/pre>\n<p>Now you need to add your username to your image tag to tell Docker where to push the contents. So if your username is <code>john<\/code>, the build command will be <code>docker build -t john\/sampleapp .<\/code>.<\/p>\n<p>In general, you use the tag like this, given <code>USER<\/code>.<\/p>\n<pre><code>$ docker build -t USER\/sampleapp .\n$ docker push USER\/sampleapp\n<\/code><\/pre>\n<p>Once you\u2019ve pushed your image, you can log into your VPS and pull the image to spin it up in production! The command is simple:<\/p>\n<pre><code>$ docker pull USER\/sampleapp\n<\/code><\/pre>\n<h2><a id=\"Wrapping_up_173\"><\/a>Wrapping up<\/h2>\n<p>Now that you understand the basics of Docker images, you can try pulling official base images for <a href=\"https:\/\/www.python.org\/\" target=\"_blank\" rel=\"noopener\">Python<\/a> or <a href=\"https:\/\/golang.org\/\" target=\"_blank\" rel=\"noopener\">Go<\/a>, for example, and try to incorporate them into your project (if it uses these languages). Similarly, even simple HTML\/CSS\/JavaScript-based websites can be containerized using an Apache web server as your base image. Play around with it a bit! Once you get the hang of \u201cDockerizing\u201d your web projects, they\u2019ll be that much easier to deploy to your VPS or share with others on GitHub or the Docker Registry.<\/p>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Any web app, blog, or even pure HTML website can be &#8220;containerized&#8221; with a Docker image to make it portable and shareable. Learn how in this quick tutorial.<\/p>\n","protected":false},"author":20,"featured_media":2361,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[18,30],"tags":[182],"class_list":["post-2355","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","category-tutorials","tag-docker"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2355","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/comments?post=2355"}],"version-history":[{"count":3,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2355\/revisions"}],"predecessor-version":[{"id":13079,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2355\/revisions\/13079"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media\/2361"}],"wp:attachment":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media?parent=2355"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/categories?post=2355"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/tags?post=2355"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}