<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Docker Containers - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Docker Containers - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 25 May 2026 10:49:59 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/docker-containers/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How Docker Containers Work – Explained for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ A container is a lightweight, standalone, and executable software package that includes everything needed to run a piece of software. And one of the most popular tools for working with containers is Docker. Docker is both the name of the company (Doc... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-docker-containers-work/</link>
                <guid isPermaLink="false">66d45e144a7504b7409c336a</guid>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ virtual machine ]]>
                    </category>
                
                    <category>
                        <![CDATA[ virtualization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Mon, 23 Oct 2023 16:45:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/cover-final.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A container is a lightweight, standalone, and executable software package that includes everything needed to run a piece of software.</p>
<p>And one of the most popular tools for working with containers is Docker.</p>
<p>Docker is both the name of the company (Docker Inc) and the software they have created which packages software into containers.</p>
<p>To understand how containers work and why they are incredibly useful for software development, you need to understand two seemingly unrelated topics – shipping containers and virtual machines.</p>
<h2 id="heading-a-brief-history-of-shipping-containers">A Brief History of Shipping Containers</h2>
<p>"The Box: How the Shipping Container Made the World Smaller and the World Economy Bigger" is a book by <a target="_blank" href="https://www.amazon.co.uk/Box-Shipping-Container-Smaller-Economy/dp/0691170819/ref=sr_1_1?crid=14VL4VEQHDVNL&amp;keywords=the+box+book&amp;qid=1694037660&amp;sprefix=the+box+book%2Caps%2C97&amp;sr=8-1">Marc Levinson</a>. It explores the profound impact of the shipping container on global trade and the world economy.</p>
<p>While the history of the shipping container may seem irrelevant in a discussion about Docker containers, they have more in common than you would expect.</p>
<p>Before shipping containers, cargo handling was labor-intensive and time-consuming, leading to inefficiencies and delays in global trade. Cargo arrived in various shapes and sizes, and the lack of standardised packaging made it challenging to stack and secure items efficiently.</p>
<p>Without standardised containers, cargo was often stored haphazardly in the holds of ships or in dockyards. This inefficient use of space meant that ships were not carrying as much cargo as they could potentially hold, leading to higher transportation costs.</p>
<p>The adoption of uniform container dimensions and handling procedures allowed for seamless transfer of cargo between different modes of transportation – ships, trucks, trains, and the cranes used to move the containers around.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac7826e-ebd0-4062-8f49-d48a6f9ef9ce_1886x946.png" alt="Image" width="1456" height="730" loading="lazy"></p>
<p><em>Image showing how standardised container sizes allow them to be easily moved between ships, trains and trucks.</em></p>
<p>This standardisation was the key to the success of shipping containers. After all, if one company’s containers didn't fit on another company's ship, truck, or freight train, they couldn't be properly transported. Every company would need its own fleet of containers to be able to send things to each of their customers – which would be an operational nightmare.</p>
<p>Standardisation of shipping containers makes them portable, that is easy to move from one place to another. This portability is a key feature of Docker containers as well, which we'll discuss shortly.</p>
<h2 id="heading-what-are-virtual-machines">What are Virtual Machines?</h2>
<p>Virtual machines (VMs) are created through a process called virtualisation.</p>
<p>Virtualisation is a technology that allows you to create multiple simulated environments or virtual versions of something, such as an operating system, a server, storage, or a network, on a single physical machine.</p>
<p>These virtual environments behave as if they are independent, separate entities, even though they share the resources of the underlying physical system.</p>
<p>Virtualisation is like having a magician's hat that can conjure up multiple hats within it. Just as the magician's hat creates the illusion of many hats appearing from just a single physical hat, virtualisation allows a single physical computer or server to appear as multiple virtual machines (VMs), each with its own operating system and resources.</p>
<p>VMs virtualise the hardware. This simply means that a VM takes a single piece of hardware – a server – and creates virtual versions of other servers running their own operating systems. Physically, it is just a single piece of hardware.</p>
<p>Logically, multiple virtual machines can run on top of a single piece of hardware. This is essentially one or more computers running within a computer, as shown below.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9733d44-d0c7-49e6-8978-da253cf9c3a9_1650x966.png" alt="Image" width="1456" height="852" loading="lazy"></p>
<p><em>Image showing how virtualisation creates several virtual machines (VMs) from a single physical server</em></p>
<h3 id="heading-how-does-virtualisation-work">How does virtualisation work?</h3>
<p>So you might be wondering – how exactly does virtualisation work? Have a look at the image below:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd74b32-e3d1-430f-bbd6-e0daf2150b82_1084x576.png" alt="Image" width="1084" height="576" loading="lazy"></p>
<p><em>Image showing how virtualisation works by virtualising a single piece of hardware to create multiple virtual machines</em></p>
<p>At the base, you have the host hardware and OS. This is the physical machine that is used to create the virtual machines. On top of this, you have the hypervisor. This allows multiple virtual machines, each with their own operating systems (OS), to run on a single physical server.</p>
<p>VMs have a few downsides, though, which containers address. Two downsides particularly stand out:</p>
<ol>
<li><p>VMs consume more resources: VMs have a higher resource overhead due to the need to run a full OS instance for each VM. This can lead to larger memory and storage consumption. This in turn can have a negative effect on performance and startup times of the virtual machine.</p>
</li>
<li><p>Portability: VMs are typically less portable due to differences in underlying OS environments. Moving VMs between different hypervisors or cloud providers can be more complex.</p>
</li>
</ol>
<p>The major cloud providers all have VMs. For AWS, it's EC2, GCP has Compute Engine, and Azure has Azure Virtual Machines.</p>
<h2 id="heading-what-are-containers">What are Containers?</h2>
<p>A container is a lightweight, standalone, and executable software package that includes everything needed to run a piece of software, including the code, runtime, system tools, and libraries.</p>
<p>Containers are designed to isolate applications and their dependencies, ensuring that they can run consistently across different environments. Whether the application is running from your computer or in the cloud, the application behaviour remains the same.</p>
<p>Unlike VMs which virtualise the hardware, <a target="_blank" href="https://aws.amazon.com/compare/the-difference-between-containers-and-virtual-machines/#:~:text=Containers%20virtualize%20the%20operating%20system,use%20your%20hardware%20resources%20efficiently.">containers virtualise the operating system</a>. This simply means that a container uses a single OS to create a virtual application and its libraries. Containers run on top of a shared OS provided by the host system.</p>
<p>This is illustrated below:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55e6ff35-1917-4374-8006-80aa8668a772_1160x470.png" alt="Image" width="1160" height="470" loading="lazy"></p>
<p><em>Image showing how containers works by virtualising the OS</em></p>
<p>The container engine allows you to spin up containers. It provides the tools and services necessary for building, running, and deploying containerised applications.</p>
<p>Containers have several benefits:</p>
<ol>
<li><p><strong>Portability</strong>: Containers are designed to be platform-independent. They can run on any system that supports the container runtime, such as Docker, regardless of the underlying operating system. This makes it easier to move applications between different environments, including local development machines, testing servers, and different cloud platforms.</p>
</li>
<li><p><strong>Efficiency</strong>: Containers share the host system's operating system, which reduces the overhead of running a virtual machine with multiple operating systems. This leads to more efficient resource utilization and allows for a higher density of applications that can run on a single host.</p>
</li>
<li><p><strong>Consistency</strong>: Containers package all the necessary components, including the application code, runtime, libraries, and dependencies, into a single unit. This eliminates the "it works on my machine" problem and ensures that the application runs consistently across different environments, from development to production.</p>
</li>
<li><p><strong>Isolation</strong>: Containers provide a lightweight and isolated environment for running applications. Each container encapsulates the application and its dependencies, ensuring that they do not interfere with each other. This isolation helps prevent conflicts and ensures consistent behaviour across different environments.</p>
</li>
<li><p><strong>Fast Deployment</strong>: Containers can be created and started quickly, often in a matter of seconds. This rapid deployment speed is particularly beneficial for applications that need to rapidly scale up or down based on demand.</p>
</li>
</ol>
<h2 id="heading-what-is-docker">What is Docker?</h2>
<p>Now that we have covered VMs and containers, what exactly is Docker? Docker is simply a tool for creating and managing containers.</p>
<p>At its core, Docker has two concepts that are useful to understand: the Dockerfile and Docker Images.</p>
<p>A Dockerfile contains the set of instructions for building a Docker Image.</p>
<p>A Docker Image serves as a template for creating Docker containers. It contains all the necessary code, runtime, system tools, libraries, and settings required to run a software application.</p>
<p>So, a Dockerfile is used to build a Docker Image which is then used as the template for creating one or more Docker containers. This is illustrated below.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f5a703a-0a08-48a0-be54-46ca4a29a9dc_1974x534.png" alt="Image" width="1456" height="394" loading="lazy"></p>
<p><em>Image showing the steps to create a docker container. First you create the Dockerfile which is used to build the Docker Image which is finally used to run a Docker container</em></p>
<p>If this explanation still causes you to scratch your head, consider the following analogy using shipping containers.</p>
<p>Imagine you need to build multiple shipping containers to transport items all over the world. You start with a document listing out the requirements for your shipping container. This will contain information like the container dimensions, type of seals, door locking mechanisms, ventilation and refrigeration requirements (if you are shipping food that needs a temperature controlled environment, for example), and so on.</p>
<p>This requirement document will then be used to create a detailed template for the container which will include engineering drawings showing the dimensions and other specifications.</p>
<p>From this template, the physical containers will then be built. This single template can be used to build one or many physical containers which will all be identical and match the specifications in the container template.</p>
<p>This is illustrated below:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa1ac249-4fd1-49f2-8b7b-e52914017f89_1944x830.png" alt="Image" width="1456" height="622" loading="lazy"></p>
<p><em>Image showing a shipping container analogue for docker containers</em></p>
<p>The Dockerfile is analogous to the requirements document, which simply has a set of instructions for building the container template.</p>
<p>The Docker Image is analogous to the container template, which details all the instructions needed for building the physical container.</p>
<p>Once created, Docker images are immutable, meaning they cannot be changed. If you need to make changes to an application, you need to modify the Dockerfile and create a new image. This immutability ensures consistency and reproducibility in application deployment.</p>
<p>And finally, the Docker container is analogous to the physical shipping container.</p>
<h2 id="heading-bringing-it-together">Bringing it Together</h2>
<p>In summary, containers provide a <strong>portable</strong> and <strong>efficient</strong> way to package applications and their dependencies, ensuring consistency across various environments. The benefits they bring to software development is similar to the benefits brought to the global economy by the humble shipping container.</p>
<h3 id="heading-portability">Portability</h3>
<p>Shipping containers, through standardisation, ensure that any container, anywhere in the world, can be seamlessly used to move items across various modes of transportation – ships, trucks, trains and the cranes used to load them on and off different forms of transport.</p>
<p>Similarly, Docker containers allow for portability. They ensure that applications can run consistently across different environments, from development laptops to production servers, and across different cloud providers.</p>
<h3 id="heading-increased-efficiency">Increased Efficiency</h3>
<p>With standard container sizes, the packing density of goods you can move increases. Now, you can squeeze more things into a single shipping container, compared to the days before the shipping container existed where you had cargo in non standard shapes and sizes stored haphazardly in the holds of ships or on dockyards. So, every ship, freight train or truck can carry more goods during every trip, making it cheaper to move goods around the world.</p>
<p>With Docker containers, better efficiency comes from the fact that containers share the host operating system, making them lightweight compared to VMs. This leads to rapid container startup times and less CPU, memory, and storage use.</p>
<p>Less resource utilisation also means that containers can increase the application density when compared to VMs. With containers, you can run more applications on the same hardware without a significant drop in performance.</p>
<p>To conclude, the shipping container by itself is not magical. After all, it is just a metal box. It is the standardisation of shipping containers which made them portable and a cheap and efficient way to move goods around the world.</p>
<p>In application development, containers benefit from standardisation in the same way. Containers provide a portable and efficient way to package applications and their dependencies, ensuring consistency across various environments.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What Does K8s Mean? How to Set Up Kubernetes and Manage Clusters ]]>
                </title>
                <description>
                    <![CDATA[ By Sebastian Sigl You might've seen the term k8s in different sources, and wondered what it means. Well, it means Kubernetes. The abbreviation consists of:     "k" which is the first letter of Kubernetes, "8" which is the number of letters between t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-does-k8s-mean-kubernetes-setup-guide/</link>
                <guid isPermaLink="false">66d461068812486a37369d64</guid>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 06 Jun 2022 19:46:37 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/05/what-does-k8s-mean.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sebastian Sigl</p>
<p>You might've seen the term k8s in different sources, and wondered what it means. Well, it means Kubernetes. The abbreviation consists of:    </p>
<ul>
<li>"<strong>k</strong>" which is the first letter of Kubernetes,</li>
<li>"<strong>8</strong>" which is the number of letters between the first and the last in the word, and</li>
<li>"<strong>s</strong>" which is the last letter.</li>
</ul>
<p>Now, let’s look into what Kubernetes is used for to understand its core strength and fields of usage.</p>
<p>Containers are everywhere. Using container technologies like <a target="_blank" href="https://www.docker.com/">Docker</a>, you can start applications in a completely isolated environment in a single command. Kubernetes is used for container orchestration, combining multiple machines into a cluster and distributing stateful containers.</p>
<p>In this blog post, you'll learn:</p>
<ul>
<li>How to play with Kubernetes on your local device,</li>
<li>How to launch applications in a Kubernetes cluster,</li>
<li>The most popular managed Kubernetes providers to run your applications at scale.</li>
</ul>
<p>This tutorial will mainly focus on using the shell rather than the UI because it allows you to use all examples in a local and remote Kubernetes cluster.</p>
<h2 id="heading-how-to-set-up-kubernetes-on-your-local-device">How to Set Up Kubernetes on your Local Device</h2>
<p>The easiest way to get started is to <a target="_blank" href="https://www.docker.com/get-started/">install Docker.</a> After starting, you can open Docker, go to <code>Settings -&gt; Kubernetes</code>, and enable Kubernetes locally. After confirming, your Kubernetes cluster will start up.</p>
<p>The easiest way to communicate with a Kubernetes cluster is by using <a target="_blank" href="https://github.com/kubernetes/kubectl">kubectl</a>. So if you haven't done it already, <a target="_blank" href="https://kubernetes.io/docs/tasks/tools/#kubectl">install kubectl</a>. </p>
<p>Additionally, I recommend that you <a target="_blank" href="https://github.com/ahmetb/kubectx">setup kubectx</a>, which allows you to switch between different clusters. To make Kubernetes deployments easy, you should also <a target="_blank" href="https://helm.sh/docs/intro/install/">install Helm</a>, which enables you to deploy more complex applications, which consist of multiple parts, with a single command.</p>
<p>Now, Docker shows that the Kubernetes cluster is ready in your settings, then you can start some applications. Let’s look at all available Kubernetes contexts:</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Show all available contexts</span>
kubectx

&gt; development
&gt; docker-desktop

<span class="hljs-comment"># Select local docker desktop context</span>
kubectx docker-desktop
</code></pre>
<h2 id="heading-how-to-launch-applications-in-a-kubernetes-cluster">How to Launch Applications in a Kubernetes Cluster</h2>
<p>Let’s set up a <a target="_blank" href="https://jupyterhub.readthedocs.io/en/stable/">JupyterHub</a> that allows a team to use Jupyter Notebooks, one of the most popular platforms for machine learning and Python.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># add helm repository</span>
helm repo add jupyterhub https://jupyterhub.github.io/helm-chart/

<span class="hljs-comment"># start containers</span>
helm install my-jupyterhub jupyterhub/jupyterhub --version 1.2.0

<span class="hljs-comment"># after a few minutes, get information about the Kubernetes service</span>
kubectl --namespace=default get svc proxy-public

NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
proxy-public   LoadBalancer   10.109.132.29   localhost     80:31480/TCP   31m
</code></pre>
<p>As printed, the external IP is localhost and exposed on port 80. Once the containers are started, you can open <a target="_blank" href="http://localhost">http://localhost</a> and log in with the username “admin” and no password.</p>
<p>As you can see, it’s effortless to run a more comprehensive application with a few command lines. If you want to try more examples, check out <a target="_blank" href="https://www.freecodecamp.org/news/helm-charts-tutorial-the-kubernetes-package-manager-explained/">my blog post about Helm</a>.</p>
<p>To clean up resources, you should remove the running application:</p>
<pre><code class="lang-sh">helm uninstall my-jupyterhub

release <span class="hljs-string">"my-jupyterhub"</span> uninstalled
</code></pre>
<h2 id="heading-most-popular-managed-kubernetes-clusters">Most Popular Managed Kubernetes Clusters</h2>
<p>Once you want to run an application and make it available to others, you must run a publicly available Kubernetes cluster. You should not run your cluster from scratch if you are not an expert.</p>
<p>Instead, you can use a managed version of a Kubernetes cluster, which means most of the maintenance of the cluster itself is done by an external provider. You can simply deploy containers, and things should just work. </p>
<p>My recommendation is to use the <a target="_blank" href="https://cloud.google.com/kubernetes-engine/">Google Kubernetes Cluster</a> (GKE), the managed cluster from Google. Google is the original designer of Kubernetes and is known to provide a solid Kubernetes experience. They also offer a more extreme managed version of it called GKE Autopilot, which allows you to pay by container hour and memory. </p>
<p>Still, in reality, teams use the standard Kubernetes cluster that comes with more integrations of other managed services, which are not available yet if you use Autopilot. For example, you can create Service Accounts, DNS Records, or Managed Certificates using Kubernetes resources by using <a target="_blank" href="https://cloud.google.com/config-connector/docs/overview">Config Connector</a>.</p>
<p>You can follow a <a target="_blank" href="https://cloud.google.com/kubernetes-engine/docs/deploy-app-cluster">simple Google tutorial to set up a GCP Kubernetes cluster and deploy an example application</a> in a few minutes.</p>
<p>There are many more managed Kubernetes clusters like <a target="_blank" href="https://aws.amazon.com/de/eks/">Elastic Kubernetes Service</a> (EKS) from Amazon and <a target="_blank" href="https://azure.microsoft.com/en-us/services/kubernetes-service/">Azure Kubernetes Service</a> (AKS) from Microsoft. Whatever cloud you prefer, it’s usually a good idea to use the managed Kubernetes cluster from your current cloud vendor.</p>
<p>I <a target="_blank" href="https://trends.google.com/trends/explore?date=today%205-y&amp;q=Google%20Kubernetes,AWS%20Kubernetes,Azure%20Kubernetes">compared the interest of Kubernetes for three popular vendors by using Google Trends</a>, which shows that Azure and AWS (Amazon) are the most popular. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/Screenshot-2022-06-05-at-11.01.31.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>These numbers do not show which vendor provides the best experience. But, they show a significant interest in Kubernetes run on Azure. Interestingly, Azure took the lead within the last months.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this blog post, you learned what k8s means – it's just an abbreviation for Kubernetes. It's a popular container orchestration system for automating applications' deployment, scaling, and management. You can run Kubernetes locally and on scale in different clouds.</p>
<p>I hope you enjoyed the article.</p>
<p>If you liked it and felt the need to give me a round of applause or just want to get in touch, <a target="_blank" href="https://twitter.com/sesigl">follow me on Twitter</a>.</p>
<p>I work at eBay Kleinanzeigen, one of the world’s biggest classified companies. By the way, <a target="_blank" href="https://www.ebay-kleinanzeigen.de/careers">we are hiring</a>!</p>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://artifacthub.io/packages/helm/jupyterhub/jupyterhub?modal=values">JupyterHub Helm Chart</a></li>
<li><a target="_blank" href="https://techgenix.com/top-managed-kubernetes-services/">Top Managed Kubernetes Service</a></li>
<li><a target="_blank" href="https://www.linkedin.com/pulse/aks-vs-eks-gke-managed-kubernetes-services-compared-yarden-fayer/?trk=public_profile_article_view">Azure vs Google vs Amazon Kubernetes</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Dockerize a Flask Application ]]>
                </title>
                <description>
                    <![CDATA[ By Ondiek Elijah Ochieng These days, developers need to develop, ship, and run applications quicker than ever. And fortunately, there's a tool that helps you do that – Docker. With Docker, you can now easily ship, test, and deploy your code quickly w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-dockerize-a-flask-app/</link>
                <guid isPermaLink="false">66d4608bc7632f8bfbf1e46d</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flask Framework ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 11 Nov 2021 16:04:40 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/11/flask-docker.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Ondiek Elijah Ochieng</p>
<p>These days, developers need to develop, ship, and run applications quicker than ever. And fortunately, there's a tool that helps you do that – Docker.</p>
<p>With Docker, you can now easily ship, test, and deploy your code quickly while maintaining full control over your infrastructure. It significantly reduces how long it takes to get from writing code to running it in production.</p>
<p>This article will show you how to make a basic Docker image and run it as a container. For the demonstration, we'll use Flask as our web framework and Docker for image creation and containerization. You'll also learn a few Docker commands that are commonly used.</p>
<h2 id="heading-what-is-flask">What is Flask?</h2>
<p><a target="_blank" href="https://flask.palletsprojects.com/en/2.0.x/"><strong>Flask</strong></a> is a popular Python micro web framework that helps you develop lightweight web applications and APIs quickly and easily. </p>
<p>As a web framework, it provides greater flexibility, customization, and scalability for simple web applications while remaining highly compatible with cutting-edge technologies.</p>
<h2 id="heading-what-is-docker">What is Docker?</h2>
<p>Docker is a tool that makes it easier to create, deploy, and run applications using containers.</p>
<p>A <strong>docker container</strong> is a collection of dependencies and code organized as software that enables applications to run quickly and efficiently in a range of computing environments.</p>
<p>A <strong>docker image</strong>, on the other hand, is a blueprint that specifies how to run an application. In order for Docker to build images automatically, a set of instructions must be stored in a special file known as a <strong>Dockerfile</strong>. </p>
<p>The instructions in this file are executed by the user on the command line interface in order to create an image. (Source: <a target="_blank" href="https://www.docker.com/resources/what-container">docker.com</a>)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/docker-illustration-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-set-up-the-project">How to Set Up the Project</h2>
<h3 id="heading-basic-directory-structure">Basic directory structure</h3>
<p>After completing the following steps, our application directory structure will look like this:</p>
<p><strong>flask-docker</strong><br>├── app.py<br>├── Dockerfile<br>├── requirements.txt<br>└── <strong>venv</strong></p>
<p>In this section, we'll go over how to create an application with a structure similar to the one shown above. You can find a detailed guide on how to create or install this project <a target="_blank" href="https://github.com/Dev-Elie/Flask-Docker-App/blob/main/README.md#create-a-new-application-from-scratch">here</a>.</p>
<p>Assuming you followed the installation instructions correctly and have an active virtual environment with Flask installed, we will now modify the two files created in the GitHub readme as follows.</p>
<h3 id="heading-how-to-modify-apppy">How to modify app.py</h3>
<p>Let's add the following lines of code to our <strong>app.py</strong>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask
app = Flask(__name__)

<span class="hljs-meta">@app.route('/')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hello_geek</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">'&lt;h1&gt;Hello from Flask &amp; Docker&lt;/h2&gt;'</span>


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(debug=<span class="hljs-literal">True</span>)
</code></pre>
<p>Now, if we run <strong>python app.py</strong> on the command line to test our Flask app, we should get results similar to the ones shown below:</p>
<pre><code class="lang-bash"> * Serving Flask app <span class="hljs-string">'app'</span> (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it <span class="hljs-keyword">in</span> a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with <span class="hljs-built_in">stat</span>
 * Debugger is active!
 * Debugger PIN: 316-584-348
</code></pre>
<h3 id="heading-how-to-modify-the-dockerfile">How to modify the Dockerfile</h3>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># syntax=docker/dockerfile:1</span>

<span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.8</span>-slim-buster

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /python-docker</span>

<span class="hljs-keyword">COPY</span><span class="bash"> requirements.txt requirements.txt</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pip3 install -r requirements.txt</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [ <span class="hljs-string">"python3"</span>, <span class="hljs-string">"-m"</span> , <span class="hljs-string">"flask"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"--host=0.0.0.0"</span>]</span>
</code></pre>
<p>Before we build an image for the application we just created, let's first understand what the lines of code in the Docker file above mean and what role they play.</p>
<p>The below code should be the first line of every Dockerfile – it tells the Docker builder what syntax to use while parsing the Dockerfile and the location of the Docker syntax file. (<a target="_blank" href="https://docs.docker.com/engine/reference/builder/#syntax">Source</a>)</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># syntax=docker/dockerfile:1</span>
</code></pre>
<p>While it is possible to create our own base images, there is no need to go that far because Docker allows us to inherit existing images. The following line tells Docker which base image to use — in this case, a Python image.</p>
<pre><code>FROM python:<span class="hljs-number">3.8</span>-slim-buster
</code></pre><p>To keep things organized, we also tell Docker which folder to use for the rest of the operations, so we use a relative path as shown below. </p>
<p>In this case, we're telling Docker to use the same directory and name for the rest of its operations — it's a directory contained within our container image.</p>
<pre><code>WORKDIR /python-docker
</code></pre><p>In the fourth and fifth lines, we tell Docker to copy the contents of our requirements.txt file into the container image's requirements.txt file. Then run pip install to install all the dependencies in the same file to be used by the image.</p>
<pre><code>COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
</code></pre><p>Continuing with the copying, we now copy the remainder of the files in our local working directory to the directory in the docker image.</p>
<pre><code>COPY . .
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2021/11/docker-illustration.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Our image so far has all of the files that are similar to those in our local working directory. Our next task is to assist Docker in understanding how to run this image inside a container. </p>
<p>This line specifically instructs Docker to run our Flask app as a module, as indicated by the "-m" tag. Then it instructs Docker to make the container available externally, such as from our browser, rather than just from within the container. We pass the host port:</p>
<pre><code>CMD [ <span class="hljs-string">"python3"</span>, <span class="hljs-string">"-m"</span> , <span class="hljs-string">"flask"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"--host=0.0.0.0"</span>]
</code></pre><p>Since we had,</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(debug=<span class="hljs-literal">True</span>)
</code></pre>
<p>Because we had the "if" statement in our application file, this will be true if we run this module as a standalone program. As a result, it can function as a module imported by another program or as a standalone program, but it will only execute the code in the if statement if run as a program. (<a target="_blank" href="https://stackoverflow.com/a/1973391/12943692">Source</a>)</p>
<h2 id="heading-how-to-build-a-docker-image">How to Build a Docker Image</h2>
<p>After that, all that remains is to build our image. Using <strong><code>docker build</code></strong>, we can now enlist Docker's help in building the image. You can combine the build command with other tags, such as the "--tag" flag, to specify the image name.</p>
<pre><code>docker build --tag python-docker .
</code></pre><h3 id="heading-how-to-run-an-image-as-a-container">How to run an image as a container</h3>
<p>Running an image inside a container is as simple as building one. But before we do so, we'd like to see what other images are available in our environment. To view images from the command line, execute the following:</p>
<pre><code>docker images
</code></pre><p>If the above command finds any images, the output should look something like this:</p>
<pre><code class="lang-bash">REPOSITORY      TAG       IMAGE ID       CREATED             SIZE
python-docker   latest    cd52b70b361a   About an hour ago   912MB
headless-cms    latest    e8b253e230ee   43 hours ago        937MB
scrappy         latest    3e7ac0d44890   7 weeks ago         904MB
python          3.9.2     587b1bc803b3   7 months ago        885MB
</code></pre>
<p>Now we can choose which image to run. Using the <strong><code>docker run</code></strong> command, we can run an image by passing the image's name as a parameter.</p>
<pre><code class="lang-docker">docker <span class="hljs-keyword">run</span>
</code></pre>
<p>While running the above command, you'll notice that on the command line it indicates that the application is running. But when you enter <a target="_blank" href="http://localhost:5000/"><code>http://localhost:5000/</code></a> on the browser, the greeting will be:</p>
<blockquote>
<p>This site can’t be reached localhost refused to connect.</p>
</blockquote>
<p>Regardless of whether the container is running, it is doing so in isolation mode and cannot connect to localhost:5000.</p>
<p>The best solution is to run the image in detached mode. Because we need to view this application in the browser rather than the container, we'll modify our docker run and add two additional tags: "-d" to run it in detached mode and "-p" to specify the port to be exposed. </p>
<p>The docker run command will now be formatted as follows:</p>
<pre><code>docker run -d -p <span class="hljs-number">5000</span>:<span class="hljs-number">5000</span> python-docker
</code></pre><p>This time, we'll see the following output if we run it in detached mode and visit localhost at port 5000:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/d.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We can use the following command to see which containers are currently running:</p>
<pre><code>docker ps
</code></pre><p>The output is as follows:</p>
<pre><code>CONTAINER ID   IMAGE           COMMAND                  CREATED         STATUS         PORTS                    NAMES
a173935297cd   python-docker   <span class="hljs-string">"python3 -m flask ru…"</span>   <span class="hljs-number">5</span> minutes ago   Up <span class="hljs-number">5</span> minutes   <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">5000</span>-&gt;<span class="hljs-number">5000</span>/tcp   happy_wescoff
</code></pre><p>To stop the currently running container, we execute this command:</p>
<pre><code>docker stop &lt;container-name&gt;
</code></pre><p>Another useful command to have when working with Docker is this one:</p>
<pre><code>docker container prune
</code></pre><p>It removes unused resources, freeing up space and keeping your system clean.</p>
<p>And that's it!</p>
<p>Thank you for taking the time to read this article. Please share, thank me in a tweet, and don't forget to follow me on Twitter <a target="_blank" href="https://twitter.com/dev_elie">@dev_elie</a>. </p>
<p>You can find more information on Docker and Python in the official <a target="_blank" href="https://docs.docker.com/language/python/">documentation</a>. I'll see you next time.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Docker Deployment Guide – How to Deploy Containers to the Cloud with AWS Lightsail ]]>
                </title>
                <description>
                    <![CDATA[ By Marcia Villalba Containers have become the de-facto way to develop applications nowadays. They provide a standard way to package all the dependencies that your application needs.  But how you deploy a containerized application to the cloud? The cl... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-do-deploy-docker-containers-to-the-cloud-with-aws-lightsail/</link>
                <guid isPermaLink="false">66d46012787a2a3b05af43c6</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ container ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 09 Feb 2021 22:45:36 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/02/ct-yt--containers--3-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Marcia Villalba</p>
<p>Containers have become the de-facto way to develop applications nowadays. They provide a standard way to package all the dependencies that your application needs. </p>
<p>But how you deploy a containerized application to the cloud? The cloud offers scalability, elasticity, and a "pay for what you use" model that is very desirable in modern applications.</p>
<p>Let's imagine that you have developed your application and packaged it in a Docker container. This application can be your website, some software that you develop for your company, or anything really. </p>
<p>This point is where most basic container and Docker tutorials end. But you want to deploy your application somewhere so others can use it. Now you'll have to start learning about Kubernetes and all the complex orchestration systems. They seem so complicated for just deploying a simple application.</p>
<p>You know that the cloud is the best place to deploy your application. Yet, most of the cloud services out there are complex. </p>
<p>To use them, you need to know specific cloud concepts such as networking, instance types, and others. So many challenges, and you just want to deploy a simple web application.</p>
<p>What if I tell you that there is a cloud service you can use to deploy your containers in a simple way? A service that provides all the benefits from the cloud, and you don't need to use any complex orchestration system to manage the container workloads. </p>
<p>If this sounds interesting, keep on reading. In this post, I will introduce you to Amazon Lightsail. And then I will show you a demo on how to deploy a container application to AWS using Lightsail. </p>
<h2 id="heading-what-is-aws">What is AWS?</h2>
<p><a target="_blank" href="https://aws.amazon.com/">AWS stands for Amazon Web Services</a> and is the most widely-adopted cloud platform. It has lots of different services that help you develop and host your applications. </p>
<p>Using the cloud for your applications has many benefits over using your own on-premise servers. For example, it helps you lower the costs of your application, become more agile, and innovate faster.</p>
<h2 id="heading-what-is-amazon-lightsail">What is Amazon Lightsail?</h2>
<p><a target="_blank" href="https://aws.amazon.com/lightsail/">Amazon Lightsail</a> is part of AWS's cloud-based offerings. It is a service that provides everything you need to deploy applications and websites to the cloud in a simple and cost effective way. </p>
<p>Even the pricing is made simpler – you know every month exactly what you are paying. Amazon Lightsail is an ideal way to deploy simple applications and websites and get started with AWS.</p>
<p>Lightsail is powered under the hood by AWS services such as virtual machines (<a target="_blank" href="https://aws.amazon.com/ec2/">Amazon EC2</a>), relational databases (<a target="_blank" href="https://aws.amazon.com/rds/">Amazon RDS</a>), and other services. It offers the same level of scalability, reliability and security that you expect from any other AWS service.</p>
<p>At the end of 2020, <a target="_blank" href="https://aws.amazon.com/blogs/aws/lightsail-containers-an-easy-way-to-run-your-containers-in-the-cloud/">Lightsail added support for deploying containers to the cloud</a>. To do this, all you need is to provide a Docker image for your containers and Lightsail will automatically deploy it for you. </p>
<p>Lightsail provides an HTTPS endpoint that is ready to serve your application. It also takes care of load balancing and orchestrating the application.</p>
<h2 id="heading-how-to-deploy-an-application-with-lightsail">How to Deploy an Application with Lightsail</h2>
<p>Let's see how Lightsail works by deploying a simple NodeJS application packaged as a container image. This image is the one that <a target="_blank" href="https://www.docker.com/101-tutorial">Docker Desktop provides</a> for learning their platform. </p>
<p>We will start this demo where most tutorials end – when your application image is hosted in <a target="_blank" href="https://hub.docker.com/">Docker Hub</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/docker1010.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-step-1-setup-your-aws-account">Step 1 - Setup your AWS account</h3>
<p>The first step in this tutorial is to get an <a target="_blank" href="https://portal.aws.amazon.com/billing/signup">AWS account</a>. In this AWS account you will be deploying your containers.</p>
<p>If you are just creating your account, the <a target="_blank" href="https://aws.amazon.com/free/">free tier</a> should be enough for this project. The free tier will give you access to a lot of AWS services for free for the first 12 months. And you will get one month of Amazon Lightsail for free.</p>
<p>Keep in mind that having an AWS account is free if you don’t use any services. You won’t be charged for creating the account, and if you don’t use the account, nothing will be charged.</p>
<p>To create an AWS account you can follow the steps in this video:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/9_wo0FHtVmY" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h3 id="heading-step-2-create-your-container-service">Step 2 - Create your container service</h3>
<p>By now you should have an AWS account and your application in Docker Hub - as this <a target="_blank" href="https://www.docker.com/101-tutorial">Docker Desktop</a> tutorial shows.</p>
<p>Log into your AWS account and go to Amazon Lightsail.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/01lightsail.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Amazon's Lightsail interface is quite different from the regular AWS interface. You can see that this interface has many tabs available. The one we are interested in for this post is <strong>Containers.</strong> But in a similar way you can create virtual instances, databases, and other cloud components using Lightsail.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/02lightsail.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now we can <strong>create a container service</strong>, and that will take us to a form where we need to make some simple decisions. </p>
<p>The first one is in which region we want to deploy our container image. Amazon Lightsail is available in all these regions, so you can pick the one that is the best for your application. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/03lightsail.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>After that, we need to choose how much power to give to the machine that will be running our containerized application. We need to decide on the size of the machine, and how many of them we need. </p>
<p>You can see how much it will cost per machine and how much memory and CPU each of them have. In addition, after you select the scale, you can see exactly how much this service will cost.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/04lightsail.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Lightsail is very clear when it comes to cost. We can see the end cost on the screen. That includes storage, load balancing, networking, and whatever this container needs to run.</p>
<p>Then we need to setup our deployment. The container service we are creating can hold up to 10 container's images. </p>
<p>For each of the containers, we need to define a name, where the image is (the URL from Docker Hub) and how we'll run and access this application. </p>
<p>In our case, we will open port 3000 with the protocol HTTP so the application can be accessed via that URL.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/05lighsail.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The last thing we need to configure is a <strong>public endpoint</strong>. You can choose from your deployment what container you want to make a public endpoint on the internet.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/06lightsail.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>After that, you are ready to <strong>start</strong> the deployment. This takes a couple of minutes. After you are done you can access the public endpoint for this service.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/07lightsail.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>At the top of the container service page, you can see the <strong>public domain</strong>. When you click that URL, you will be accessing the application you defined in the public endpoint. </p>
<p>If you need your containers to talk to each other without making them public, use the <strong>private domain</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/08lightsial.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Now you have a container application deployed in the cloud. This application is scalable. You can monitor the usage of the power you defined before in the <strong>Metrics</strong> tab. You can always modify these specs if you see that you need more or less power.</p>
<p>In addition, if you need a domain for your application, you can get one from the <a target="_blank" href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/understanding-dns-in-amazon-lightsail">Amazon Lightsail console</a>. </p>
<p>If you want to watch this blog post as a video you can check it out here where we do the same together. <a target="_blank" href="https://www.youtube.com/watch?v=V-C_ZJi6-o0&amp;t=432s">This video is also available in Spanish if you prefer</a>. </p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/xMudAoq-vmI" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p><strong>Thanks for reading.</strong></p>
<p>I’m Marcia Villalba, Developer Advocate for AWS and the host of a YouTube channel called FooBar. There I published over 300 video tutorials about Serverless, AWS and software engineer practices.</p>
<ul>
<li>Twitter: <a target="_blank" href="https://twitter.com/mavi888uy">https://twitter.com/mavi888uy</a></li>
<li>Youtube: <a target="_blank" href="https://youtube.com/foobar_codes">https://youtube.com/foobar_codes</a></li>
<li>Spanish Youtube channel: <a target="_blank" href="https://bit.ly/aws-esp-yt">https://bit.ly/aws-esp-yt</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Docker Handbook – Learn Docker for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ The concept of containerization itself is pretty old. But the emergence of the Docker Engine in 2013 has made it much easier to containerize your applications. According to the Stack Overflow Developer Survey - 2020, Docker is the #1 most wanted plat... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-docker-handbook/</link>
                <guid isPermaLink="false">66b0ab4d8d675d0da5f1ab81</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Farhan Hasin Chowdhury ]]>
                </dc:creator>
                <pubDate>Mon, 01 Feb 2021 16:35:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/The-Docker-Handbook-Mockup.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The concept of containerization itself is pretty old. But the emergence of the <a target="_blank" href="https://docs.docker.com/get-started/overview/#docker-engine">Docker Engine</a> in 2013 has made it much easier to containerize your applications.</p>
<p>According to the <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#overview">Stack Overflow Developer Survey - 2020</a>, <a target="_blank" href="https://docker.com/">Docker</a> is the <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-wanted5">#1 most wanted platform</a>, <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-loved5">#2 most loved platform</a>, and also the <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#technology-platforms">#3 most popular platform</a>.</p>
<p>As in-demand as it may be, getting started can seem a bit intimidating at first. So in this book, we'll be learning everything from the basics to a more intermediate level of containerization. After going through the entire book, you should be able to:</p>
<ul>
<li>Containerize (almost) any application</li>
<li>Upload custom Docker Images to online registries</li>
<li>Work with multiple containers using Docker Compose</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>Familiarity with the Linux Terminal</li>
<li>Familiarity with JavaScript (some later projects use JavaScript)</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-introduction-to-containerization-and-docker">Introduction to Containerization and Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-docker">How to Install Docker</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-install-docker-on-macos">How to Install Docker on macOS</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-docker-on-windows">How to Install Docker on Windows</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-docker-on-linux">How to Install Docker on Linux</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-hello-world-in-docker-intro-to-docker-basics">Hello World in Docker - Intro to Docker Basics</a><ul>
<li><a class="post-section-overview" href="#heading-what-is-a-container">What is a Container?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-a-docker-image">What is a Docker Image?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-a-docker-registry">What is a Docker Registry?</a></li>
<li><a class="post-section-overview" href="#heading-docker-architecture-overview">Docker Architecture Overview</a></li>
<li><a class="post-section-overview" href="#heading-the-full-picture">The Full Picture</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-docker-container-manipulation-basics">Docker Container Manipulation Basics</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-run-a-container">How to Run a Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-publish-a-port">How to Publish a Port</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-detached-mode">How to Use Detached Mode</a></li>
<li><a class="post-section-overview" href="#heading-how-to-list-containers">How to List Containers</a></li>
<li><a class="post-section-overview" href="#heading-how-to-name-or-rename-a-container">How to Name or Rename a Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-stop-or-kill-a-running-container">How to Stop or Kill a Running Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-restart-a-container">How to Restart a Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-container-without-running">How to Create a Container Without Running</a></li>
<li><a class="post-section-overview" href="#heading-how-to-remove-dangling-containers">How to Remove Dangling Containers</a></li>
<li><a class="post-section-overview" href="#heading-how-to-run-a-container-in-interactive-mode">How to Run a Container in Interactive Mode</a></li>
<li><a class="post-section-overview" href="#heading-how-to-execute-commands-inside-a-container">How to Execute Commands Inside a Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-executable-images">How to Work With Executable Images</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-docker-image-manipulation-basics">Docker Image Manipulation Basics</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-create-a-docker-image">How to Create a Docker Image</a></li>
<li><a class="post-section-overview" href="#heading-how-to-tag-docker-images">How to Tag Docker Images</a></li>
<li><a class="post-section-overview" href="#heading-how-to-list-and-remove-docker-images">How to List and Remove Docker Images</a></li>
<li><a class="post-section-overview" href="#heading-how-to-understand-the-many-layers-of-a-docker-image">How to Understand the Many Layers of a Docker Image</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-nginx-from-source">How to Build NGINX from Source</a></li>
<li><a class="post-section-overview" href="#heading-how-to-optimize-docker-images">How to Optimize Docker Images</a></li>
<li><a class="post-section-overview" href="#heading-embracing-alpine-linux">Embracing Alpine Linux</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-executable-docker-images">How to Create Executable Docker Images</a></li>
<li><a class="post-section-overview" href="#heading-how-to-share-your-docker-images-online">How to Share Your Docker Images Online</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-containerize-a-javascript-application">How to Containerize a JavaScript Application</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-write-the-development-dockerfile">How to Write the Development Dockerfile</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-bind-mounts-in-docker">How to Work With Bind Mounts in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-anonymous-volumes-in-docker">How to Work With Anonymous Volumes in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-perform-multi-staged-builds-in-docker">How to Perform Multi-Staged Builds in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-ignore-unnecessary-files">How to Ignore Unnecessary Files</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-network-manipulation-basics-in-docker">Network Manipulation Basics in Docker</a><ul>
<li><a class="post-section-overview" href="#heading-docker-network-basics">Docker Network Basics</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-user-defined-bridge-in-docker">How to Create a User-Defined Bridge in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-attach-a-container-to-a-network-in-docker">How to Attach a Container to a Network in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-detach-containers-from-a-network-in-docker">How to Detach Containers from a Network in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-get-rid-of-networks-in-docker">How to Get Rid of Networks in Docker</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-containerize-a-multi-container-javascript-application">How to Containerize a Multi-Container JavaScript Application</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-run-the-database-server">How to Run the Database Server</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-named-volumes-in-docker">How to Work with Named Volumes in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-access-logs-from-a-container-in-docker">How to Access Logs from a Container in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-network-and-attaching-the-database-server-in-docker">How to Create a Network and Attaching the Database Server in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-write-the-dockerfile">How to Write the Dockerfile</a></li>
<li><a class="post-section-overview" href="#heading-how-to-execute-commands-in-a-running-container">How to Execute Commands in a Running Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-write-management-scripts-in-docker">How to Write Management Scripts in Docker</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-compose-projects-using-docker-compose">How to Compose Projects Using Docker-Compose</a><ul>
<li><a class="post-section-overview" href="#heading-docker-compose-basics">Docker Compose Basics</a></li>
<li><a class="post-section-overview" href="#heading-how-to-start-services-in-docker-compose">How to Start Services in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-list-services-in-docker-compose">How to List Services in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-execute-commands-inside-a-running-service-in-docker-compose">How to Execute Commands Inside a Running Service in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-access-logs-from-a-running-service-in-docker-compose">How to Access Logs from a Running Service in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-stop-services-in-docker-compose">How to Stop Services in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-compose-a-full-stack-application-in-docker-compose">How to Compose a Full-stack Application in Docker Compose</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-project-code">Project Code</h2>
<p>Code for the example projects can be found in the following repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/fhsinchy/docker-handbook-projects/">https://github.com/fhsinchy/docker-handbook-projects/</a></div>
<p>You can find the complete code in the <code>completed</code> branch.</p>
<h2 id="heading-contributions">Contributions</h2>
<p>This book is completely open-source and quality contributions are more than welcome. You can find the full content in the following repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/fhsinchy/the-docker-handbook">https://github.com/fhsinchy/the-docker-handbook</a></div>
<p>I usually do my changes and updates on the GitBook version of the book first and then publish them on freeCodeCamp. You can find the always updated and often unstable version of the book at the following link:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docker-handbook.farhan.dev/">https://docker-handbook.farhan.dev/</a></div>
<p>If you're looking for a frozen but stable version of the book, then freeCodeCamp will be the best place to go:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.freecodecamp.org/news/the-docker-handbook/">https://www.freecodecamp.org/news/the-docker-handbook/</a></div>
<p>Whichever version of the book you end up reading though, don't forget to let me know your opinion. Constructive criticism is always welcomed.</p>
<h2 id="heading-introduction-to-containerization-and-docker">Introduction to Containerization and Docker</h2>
<p>According to <a target="_blank" href="https://www.ibm.com/cloud/learn/containerization#toc-what-is-co-r25Smlqq">IBM</a>, </p>
<blockquote>
<p>Containerization involves encapsulating or packaging up software code and all its dependencies so that it can run uniformly and consistently on any infrastructure.</p>
</blockquote>
<p>‌In other words, containerization lets you bundle up your software along with all its dependencies in a self-contained package so that it can be run without going through a troublesome setup process.</p>
<p>‌Let's consider a real life scenario here. Assume you have developed an awesome book management application that can store information regarding all the books you own, and can also serve the purpose of a book lending system for your friends.</p>
<p>‌If you make a list of the dependencies, that list may look as follows:</p>
<ul>
<li>Node.js</li>
<li>Express.js</li>
<li>SQLite3</li>
</ul>
<p>Well, theoretically this should be it. But practically there are some other things as well. Turns out <a target="_blank" href="https://nodejs.org/">Node.js</a> uses a build tool known as <code>node-gyp</code> for building native add-ons. And according to the <a target="_blank" href="https://github.com/nodejs/node-gyp#installation">installation instruction</a> in the <a target="_blank" href="https://github.com/nodejs/node-gyp">official repository</a>, this build tool requires Python 2 or 3 and a proper C/C++ compiler tool-chain. </p>
<p>Taking all these into account, the final list of dependencies is as follows:</p>
<ul>
<li>Node.js</li>
<li>Express.js</li>
<li>SQLite3</li>
<li>Python 2 or 3</li>
<li>C/C++ tool-chain</li>
</ul>
<p>Installing Python 2 or 3 is pretty straightforward regardless of the platform you're on. Setting up the C/C++ tool-chain is pretty easy on Linux, but on Windows and Mac it's a painful task. </p>
<p>On Windows, the C++ build tools package measures at gigabytes and takes quite some time to install. On a Mac, you can either install the gigantic <a target="_blank" href="https://developer.apple.com/xcode/">Xcode</a> application or the much smaller <a target="_blank" href="https://developer.apple.com/downloads/">Command Line Tools for Xcode</a> package. </p>
<p>Regardless of the one you install, it still may break on OS updates. In fact, the problem is so prevalent that there are <a target="_blank" href="https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md">Installation notes for macOS Catalina</a> available on the official repository.</p>
<p>Let's assume that you've gone through all the hassle of setting up the dependencies and have started working on the project. Does that mean you're out of danger now? Of course not.</p>
<p>What if you have a teammate who uses Windows while you're using Linux. Now you have to consider the inconsistencies of how these two different operating systems handle paths. Or the fact that popular technologies like <a target="_blank" href="https://nginx.org/">nginx</a> are not well optimized to run on Windows. Some technologies like <a target="_blank" href="https://redis.io/">Redis</a> don't even come pre-built for Windows.</p>
<p>Even if you get through the entire development phase, what if the person responsible for managing the servers follows the wrong deployment procedure?</p>
<p>All these issues can be solved if only you could somehow:</p>
<ul>
<li>Develop and run the application inside an isolated environment (known as a container) that matches your final deployment environment.</li>
<li>Put your application inside a single file (known as an image) along with all its dependencies and necessary deployment configurations.</li>
<li>And share that image through a central server (known as a registry) that is accessible by anyone with proper authorization.</li>
</ul>
<p>Your teammates will then be able to download the image from the registry, run the application as it is within an isolated environment free from the platform specific inconsistencies, or even deploy directly on a server, since the image comes with all the proper production configurations.</p>
<p>That is the idea behind containerization: putting your applications inside a self-contained package, making it portable and reproducible across various environments.</p>
<p><strong>Now the question is "What role does Docker play here?"</strong></p>
<p>As I've already explained, containerization is an idea that solves a myriad of problems in software development by putting things into boxes. </p>
<p>This very idea has quite a few implementations. <a target="_blank" href="https://www.docker.com/">Docker</a> is such an implementation. It's an open-source containerization platform that allows you to containerize your applications, share them using public or private registries, and also to <a target="_blank" href="https://docs.docker.com/get-started/orchestration/">orchestrate</a> them.</p>
<p>Now, Docker is not the only containerization tool on the market, it's just the most popular one. Another containerization engine that I love is called <a target="_blank" href="https://podman.io/">Podman</a> developed by Red Hat. Other tools like <a target="_blank" href="https://github.com/GoogleContainerTools/kaniko">Kaniko</a> by Google, <a target="_blank" href="https://coreos.com/rkt/">rkt</a> by CoreOS are amazing, but they're not ready to be a drop-in replacement for Docker just yet.</p>
<p>Also, if you want a history lesson, you may read the amazing <a target="_blank" href="https://blog.aquasec.com/a-brief-history-of-containers-from-1970s-chroot-to-docker-2016">A Brief History of Containers: From the 1970s Till Now</a> which covers most of the major turning points for the technology.</p>
<h2 id="heading-how-to-install-docker">How to Install Docker</h2>
<p>Installation of Docker varies greatly depending on the operating system you’re using. But it's universally simple across the board. </p>
<p>Docker runs flawlessly on all three major platforms, Mac, Windows, and Linux. Among the three, the installation process on Mac is the easiest, so we'll start there.</p>
<h3 id="heading-how-to-install-docker-on-macos">How to Install Docker on macOS</h3>
<p>On a mac, all you have to do is navigate to the official <a target="_blank" href="https://www.docker.com/products/docker-desktop">download page</a> and click the <em>Download for Mac (stable)</em> button. </p>
<p>You’ll get a regular looking <em>Apple Disk Image</em> file and inside the file, there will be the application. All you have to do is drag the file and drop it in your Applications directory.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/drag-docker-in-applications-directory.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can start Docker by simply double-clicking the application icon. Once the application starts, you'll see the Docker icon appear on your menu-bar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-icon-in-menubar.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, open up the terminal and execute <code>docker --version</code> and <code>docker-compose --version</code> to ensure the success of the installation.</p>
<h3 id="heading-how-to-install-docker-on-windows">How to Install Docker on Windows</h3>
<p>On Windows, the procedure is almost the same, except there are a few extra steps that you’ll need to go through. The installation steps are as follows:</p>
<ol>
<li>Navigate to <a target="_blank" href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">this site</a> and follow the instructions for installing WSL2 on Windows 10.</li>
<li>Then navigate to the official <a target="_blank" href="https://www.docker.com/products/docker-desktop">download page</a> and click the <em>Download for Windows (stable)</em> button.</li>
<li>Double-click the downloaded installer and go through the installation with the defaults.</li>
</ol>
<p>Once the installation is done, start <em>Docker Desktop</em> either from the start menu or your desktop. The docker icon should show up on your taskbar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-icon-in-taskbar.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, open up Ubuntu or whatever distribution you've installed from Microsoft Store. Execute the <code>docker --version</code> and <code>docker-compose --version</code> commands to make sure that the installation was successful.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-and-compose-version-on-windows.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can access Docker from your regular Command Prompt or PowerShell as well. It's just that I prefer using WSL2 over any other command line on Windows.</p>
<h3 id="heading-how-to-install-docker-on-linux">How to Install Docker on Linux</h3>
<p>Installing Docker on Linux is a bit of a different process, and depending on the distribution you’re on, it may vary even more. But to be honest, the installation is just as easy (if not easier) as the other two platforms.</p>
<p>The Docker Desktop package on Windows or Mac is a collection of tools like <code>Docker Engine</code>, <code>Docker Compose</code>, <code>Docker Dashboard</code>, <code>Kubernetes</code> and a few other goodies. </p>
<p>On Linux however, you don’t get such a bundle. Instead you install all the necessary tools you need manually. Installation procedures for different distributions are as follows:</p>
<ul>
<li>If you’re on Ubuntu, you may follow the <a target="_blank" href="https://docs.docker.com/engine/install/ubuntu/">Install Docker Engine on Ubuntu</a> section from the official docs.</li>
<li>For other distributions, <em>installation per distro</em> guides are available on the official docs.<ul>
<li><a target="_blank" href="https://docs.docker.com/engine/install/debian/">Install Docker Engine on Debian</a></li>
<li><a target="_blank" href="https://docs.docker.com/engine/install/fedora/">Install Docker Engine on Fedora</a></li>
<li><a target="_blank" href="https://docs.docker.com/engine/install/centos/">Install Docker Engine on CentOS</a></li>
</ul>
</li>
<li>If you’re on a distribution that is not listed in the docs, you may follow the <a target="_blank" href="https://docs.docker.com/engine/install/binaries/">Install Docker Engine from binaries</a> guide instead.</li>
<li>Regardless of the procedure you follow, you’ll have to go through some <a target="_blank" href="https://docs.docker.com/engine/install/linux-postinstall/">Post-installation steps for Linux</a> which are very important.</li>
<li>Once you’re done with the docker installation, you’ll have to install another tool named Docker Compose. You may follow the <a target="_blank" href="https://docs.docker.com/compose/install/">Install Docker Compose</a> guide from the official docs. </li>
</ul>
<p>Once the installation is done, open up the terminal and execute <code>docker --version</code> and <code>docker-compose --version</code> to ensure the success of the installation.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-and-compose-version-on-linux.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Although Docker performs quite well regardless of the platform you’re on, I prefer Linux over the others. Throughout the book, I’ll be switching between my <a target="_blank" href="https://releases.ubuntu.com/20.10/">Ubuntu 20.10</a> and <a target="_blank" href="https://fedoramagazine.org/announcing-fedora-33/">Fedora 33</a> workstations.</p>
<p>Another thing that I would like to clarify right from the get go, is that I won't be using any GUI tool for working with Docker throughout the entire book.</p>
<p>I'm aware of the nice GUI tools available for different platforms, but learning the common docker commands is one of the primary goals of this book.</p>
<h2 id="heading-hello-world-in-docker-intro-to-docker-basics">Hello World in Docker – Intro to Docker Basics</h2>
<p>Now that you have Docker up and running on your machine, it's time for you to run your first container. Open up the terminal and run the following command:</p>
<pre><code>docker run hello-world

# Unable to find image <span class="hljs-string">'hello-world:latest'</span> locally
# latest: Pulling <span class="hljs-keyword">from</span> library/hello-world
# <span class="hljs-number">0e03</span>bdcc26d7: Pull complete 
# Digest: sha256:<span class="hljs-number">4</span>cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc
# Status: Downloaded newer image <span class="hljs-keyword">for</span> hello-world:latest
# 
# Hello <span class="hljs-keyword">from</span> Docker!
# This message shows that your installation appears to be working correctly.
# 
# To generate <span class="hljs-built_in">this</span> message, Docker took the following steps:
#  <span class="hljs-number">1.</span> The Docker client contacted the Docker daemon.
#  <span class="hljs-number">2.</span> The Docker daemon pulled the <span class="hljs-string">"hello-world"</span> image <span class="hljs-keyword">from</span> the Docker Hub.
#     (amd64)
#  <span class="hljs-number">3.</span> The Docker daemon created a <span class="hljs-keyword">new</span> container <span class="hljs-keyword">from</span> that image which runs the
#     executable that produces the output you are currently reading.
#  <span class="hljs-number">4.</span> The Docker daemon streamed that output to the Docker client, which sent it
#     to your terminal.
#
# To <span class="hljs-keyword">try</span> something more ambitious, you can run an Ubuntu container <span class="hljs-keyword">with</span>:
#  $ docker run -it ubuntu bash
# 
# Share images, automate workflows, and more <span class="hljs-keyword">with</span> a free Docker ID:
#  https:<span class="hljs-comment">//hub.docker.com/</span>
#
# For more examples and ideas, <span class="hljs-attr">visit</span>:
#  https:<span class="hljs-comment">//docs.docker.com/get-started/</span>
</code></pre><p>The <a target="_blank" href="https://hub.docker.com/_/hello-world">hello-world</a> image is an example of minimal containerization with Docker. It has a single program compiled from a <a target="_blank" href="https://github.com/docker-library/hello-world/blob/master/hello.c">hello.c</a> file responsible for printing out the message you're seeing on your terminal.</p>
<p>Now in your terminal, you can use the <code>docker ps -a</code> command to have a look at all the containers that are currently running or have run in the past:</p>
<pre><code>docker ps -a

# CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
# <span class="hljs-number">128</span>ec8ceab71        hello-world         <span class="hljs-string">"/hello"</span>            <span class="hljs-number">14</span> seconds ago      Exited (<span class="hljs-number">0</span>) <span class="hljs-number">13</span> seconds ago                      exciting_chebyshev
</code></pre><p>In the output, a container named <code>exciting_chebyshev</code> was run with the container id of <code>128ec8ceab71</code> using the <code>hello-world</code> image. It has <code>Exited (0) 13 seconds ago</code> where the <code>(0)</code> exit code means no error was produced during the runtime of the container.</p>
<p>Now in order to understand what just happened behind the scenes, you'll have to get familiar with the Docker Architecture and three very fundamental concepts of containerization in general, which are as follows:</p>
<ul>
<li>Container</li>
<li>Image</li>
<li>Registry</li>
</ul>
<p>I've listed the three concepts in alphabetical order and will begin my explanations with the first one on the list.</p>
<h3 id="heading-what-is-a-container">What is a Container?</h3>
<p>In the world of containerization, there can not be anything more fundamental than the concept of a container.</p>
<p>The official Docker <a target="_blank" href="https://www.docker.com/resources/what-container">resources</a> site says - </p>
<blockquote>
<p>A container is an abstraction at the application layer that packages code and dependencies together. Instead of virtualizing the entire physical machine, containers virtualize the host operating system only.</p>
</blockquote>
<p>You may consider containers to be the next generation of virtual machines. </p>
<p>Just like virtual machines, containers are completely isolated environments from the host system as well as from each other. They are also a lot lighter than the traditional virtual machine, so a large number of containers can be run simultaneously without affecting the performance of the host system.‌</p>
<p>Containers and virtual machines are actually different ways of virtualizing your physical hardware. The main difference between these two is the method of virtualization.</p>
<p>Virtual machines are usually created and managed by a program known as a hypervisor, like <a target="_blank" href="https://www.virtualbox.org/">Oracle VM VirtualBox</a>, <a target="_blank" href="https://www.vmware.com/">VMware Workstation</a>, <a target="_blank" href="https://www.linux-kvm.org/">KVM</a>, <a target="_blank" href="https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/about/">Microsoft Hyper-V</a> and so on. This hypervisor program usually sits between the host operating system and the virtual machines to act as a medium of communication.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/virtual-machines.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Each virtual machine comes with its own guest operating system which is just as heavy as the host operating system. </p>
<p>The application running inside a virtual machine communicates with the guest operating system, which talks to the hypervisor, which then in turn talks to the host operating system to allocate necessary resources from the physical infrastructure to the running application.</p>
<p>As you can see, there is a long chain of communication between applications running inside virtual machines and the physical infrastructure. The application running inside the virtual machine may take only a small amount of resources, but the guest operating system adds a noticeable overhead.</p>
<p>Unlike a virtual machine, a container does the job of virtualization in a smarter way. Instead of having a complete guest operating system inside a container, it just utilizes the host operating system via the container runtime while maintaining isolation – just like a traditional virtual machine.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/containers.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The container runtime, that is Docker, sits between the containers and the host operating system instead of a hypervisor. The containers then communicate with the container runtime which then communicates with the host operating system to get necessary resources from the physical infrastructure. </p>
<p>As a result of eliminating the entire guest operating system layer, containers are much lighter and less resource-hogging than traditional virtual machines.</p>
<p>As a demonstration of the point, look at the following code block:</p>
<pre><code>uname -a
# Linux alpha-centauri <span class="hljs-number">5.8</span><span class="hljs-number">.0</span><span class="hljs-number">-22</span>-generic #<span class="hljs-number">23</span>-Ubuntu SMP Fri Oct <span class="hljs-number">9</span> <span class="hljs-number">00</span>:<span class="hljs-number">34</span>:<span class="hljs-number">40</span> UTC <span class="hljs-number">2020</span> x86_64 x86_64 x86_64 GNU/Linux

docker run alpine uname -a
# Linux f08dbbe9199b <span class="hljs-number">5.8</span><span class="hljs-number">.0</span><span class="hljs-number">-22</span>-generic #<span class="hljs-number">23</span>-Ubuntu SMP Fri Oct <span class="hljs-number">9</span> <span class="hljs-number">00</span>:<span class="hljs-number">34</span>:<span class="hljs-number">40</span> UTC <span class="hljs-number">2020</span> x86_64 Linux
</code></pre><p>In the code block above, I have executed the <code>uname -a</code> command on my host operating system to print out the kernel details. Then on the next line I've executed the same command inside a container running <a target="_blank" href="https://alpinelinux.org/">Alpine Linux</a>. </p>
<p>As you can see in the output, the container is indeed using the kernel from my host operating system. This goes to prove the point that containers virtualize the host operating system instead of having an operating system of their own.</p>
<p>If you're on a Windows machine, you'll find out that all the containers use the WSL2 kernel. It happens because WSL2 acts as the back-end for Docker on Windows. On macOS the default back-end is a VM running on <a target="_blank" href="https://github.com/moby/hyperkit">HyperKit</a> hypervisor.</p>
<h3 id="heading-what-is-a-docker-image">What is a Docker Image?</h3>
<p>Images are multi-layered self-contained files that act as the template for creating containers. They are like a frozen, read-only copy of a container. Images can be exchanged through registries.</p>
<p>In the past, different container engines had different image formats. But later on, the <a target="_blank" href="https://opencontainers.org/">Open Container Initiative (OCI)</a> defined a standard specification for container images which is complied by the major containerization engines out there. This means that an image built with Docker can be used with another runtime like Podman without any additional hassle.</p>
<p>Containers are just images in running state. When you obtain an image from the internet and run a container using that image, you essentially create another temporary writable layer on top of the previous read-only ones. </p>
<p>This concept will become a lot clearer in upcoming sections of this book. But for now, just keep in mind that images are multi-layered read-only files carrying your application in a desired state inside them.</p>
<h3 id="heading-what-is-a-docker-registry">What is a Docker Registry?</h3>
<p>You've already learned about two very important pieces of the puzzle, <em>Containers</em> and <em>Images</em>. The final piece is the <em>Registry</em>. </p>
<p>An image registry is a centralized place where you can upload your images and can also download images created by others. <a target="_blank" href="https://hub.docker.com/">Docker Hub</a> is the default public registry for Docker. Another very popular image registry is <a target="_blank" href="https://quay.io/">Quay</a> by Red Hat. </p>
<p>Throughout this book I'll be using Docker Hub as my registry of choice.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-hub.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can share any number of public images on Docker Hub for free. People around the world will be able to download them and use them freely. Images that I've uploaded are available on my profile (<a target="_blank" href="https://hub.docker.com/u/fhsinchy">fhsinchy</a>) page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/my-images-on-docker-hub.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Apart from Docker Hub or Quay, you can also create your own image registry for hosting private images. There is also a local registry that runs within your computer that caches images pulled from remote registries.</p>
<h3 id="heading-docker-architecture-overview">Docker Architecture Overview</h3>
<p>Now that you've become familiar with most of the fundamental concepts regarding containerization and Docker, it's time for you to understand how Docker as a software was designed.</p>
<p>The engine consists of three major components:</p>
<ol>
<li><strong>Docker Daemon:</strong> The daemon (<code>dockerd</code>) is a process that keeps running in the background and waits for commands from the client. The daemon is capable of managing various Docker objects.</li>
<li><strong>Docker Client:</strong> The client  (<code>docker</code>) is a command-line interface program mostly responsible for transporting commands issued by users.</li>
<li><strong>REST API:</strong> The REST API acts as a bridge between the daemon and the client. Any command issued using the client passes through the API to finally reach the daemon.</li>
</ol>
<p>According to the official <a target="_blank" href="https://docs.docker.com/get-started/overview/#docker-architecture">docs</a>, </p>
<blockquote>
<p>"Docker uses a client-server architecture. The Docker <em>client</em> talks to the Docker <em>daemon</em>, which does the heavy lifting of building, running, and distributing your Docker containers".</p>
</blockquote>
<p>You as a user will usually execute commands using the client component. The client then use the REST API to reach out to the long running daemon and get your work done.</p>
<h3 id="heading-the-full-picture">The Full Picture</h3>
<p>Okay, enough talking. Now it's time for you to understand how all these pieces of the puzzle you just learned about work in harmony. Before I dive into the explanation of what really happens when you run the <code>docker run hello-world</code> command, let me show you a little diagram I've made:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-run-hello-world.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This image is a slightly modified version of the one found in the official <a target="_blank" href="https://docs.docker.com/engine/images/architecture.svg">docs</a>. The events that occur when you execute the command are as follows:</p>
<ol>
<li>You execute <code>docker run hello-world</code> command where <code>hello-world</code> is the name of an image.</li>
<li>Docker client reaches out to the daemon, tells it to get the <code>hello-world</code> image and run a container from that.</li>
<li>Docker daemon looks for the image within your local repository and realizes that it's not there, resulting in the <code>Unable to find image 'hello-world:latest' locally</code> that's printed on your terminal.</li>
<li>The daemon then reaches out to the default public registry which is Docker Hub and pulls in the latest copy of the <code>hello-world</code> image, indicated by the <code>latest: Pulling from library/hello-world</code> line in your terminal.</li>
<li>Docker daemon then creates a new container from the freshly pulled image.</li>
<li>Finally Docker daemon runs the container created using the <code>hello-world</code> image outputting the wall of text on your terminal.</li>
</ol>
<p>It's the default behavior of Docker daemon to look for images in the hub that are not present locally. But once an image has been fetched, it'll stay in the local cache. So if you execute the command again, you won't see the following lines in the output:</p>
<pre><code>Unable to find image <span class="hljs-string">'hello-world:latest'</span> locally
<span class="hljs-attr">latest</span>: Pulling <span class="hljs-keyword">from</span> library/hello-world
<span class="hljs-number">0e03</span>bdcc26d7: Pull complete
<span class="hljs-attr">Digest</span>: sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9
<span class="hljs-attr">Status</span>: Downloaded newer image <span class="hljs-keyword">for</span> hello-world:latest
</code></pre><p>If there is a newer version of the image available on the public registry, the daemon will fetch the image again. That <code>:latest</code> is a tag. Images usually have meaningful tags to indicate versions or builds. You'll learn about this in greater detail later on.</p>
<h2 id="heading-docker-container-manipulation-basics">Docker Container Manipulation Basics</h2>
<p>In the previous sections, you've learned about the building blocks of Docker and have also run a container using the <code>docker run</code> command. </p>
<p>In this section, you'll be learning about container manipulation in a lot more detail. Container manipulation is one of the most common task you'll be performing every single day, so having a proper understanding of the various commands is crucial.</p>
<p>Keep in mind, though, that this is not an exhaustive list of all the commands you can execute on Docker. I'll be talking only about the most common ones. Anytime you want to learn more about the available commands, just visit the official <a target="_blank" href="https://docs.docker.com/engine/reference/commandline/container/">reference</a> for the Docker command-line.</p>
<h3 id="heading-how-to-run-a-container">How to Run a Container</h3>
<p>Previously you've used <code>docker run</code> to create and start a container using the <code>hello-world</code> image. The generic syntax for this command is as follows:</p>
<pre><code>docker run &lt;image name&gt;
</code></pre><p>Although this is a perfectly valid command, there is a better way of dispatching commands to the <code>docker</code> daemon. </p>
<p>Prior to version <code>1.13</code>, Docker had only the previously mentioned command syntax. Later on, the command-line was <a target="_blank" href="https://www.docker.com/blog/whats-new-in-docker-1-13/">restructured</a> to have the following syntax:</p>
<pre><code>docker &lt;object&gt; &lt;command&gt; &lt;options&gt;
</code></pre><p>In this syntax:</p>
<ul>
<li><code>object</code> indicates the type of Docker object you'll be manipulating. This can be a <code>container</code>, <code>image</code>, <code>network</code> or <code>volume</code> object.</li>
<li><code>command</code> indicates the task to be carried out by the daemon, that is the <code>run</code> command.</li>
<li><code>options</code> can be any valid parameter that can override the default behavior of the command, like the <code>--publish</code> option for port mapping.</li>
</ul>
<p>Now, following this syntax, the <code>run</code> command can be written as follows:</p>
<pre><code>docker container run &lt;image name&gt;
</code></pre><p>The <code>image name</code> can be of any image from an online registry or your local system. As an example, you can try to run a container using the <a target="_blank" href="https://hub.docker.com/r/fhsinchy/hello-dock">fhsinchy/hello-dock</a> image. This image contains a simple <a target="_blank" href="https://vuejs.org/">Vue.js</a> application that runs on port 80 inside the container. </p>
<p>To run a container using this image, execute following command on your terminal:</p>
<pre><code>docker container run --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> fhsinchy/hello-dock

# /docker-entrypoint.sh: <span class="hljs-regexp">/docker-entrypoint.d/</span> is not empty, will attempt to perform configuration
# /docker-entrypoint.sh: Looking <span class="hljs-keyword">for</span> shell scripts <span class="hljs-keyword">in</span> /docker-entrypoint.d/
# /docker-entrypoint.sh: Launching /docker-entrypoint.d/<span class="hljs-number">10</span>-listen-on-ipv6-by-<span class="hljs-keyword">default</span>.sh
# <span class="hljs-number">10</span>-listen-on-ipv6-by-<span class="hljs-keyword">default</span>.sh: Getting the checksum <span class="hljs-keyword">of</span> /etc/nginx/conf.d/<span class="hljs-keyword">default</span>.conf
# <span class="hljs-number">10</span>-listen-on-ipv6-by-<span class="hljs-keyword">default</span>.sh: Enabled listen on IPv6 <span class="hljs-keyword">in</span> /etc/nginx/conf.d/<span class="hljs-keyword">default</span>.conf
# /docker-entrypoint.sh: Launching /docker-entrypoint.d/<span class="hljs-number">20</span>-envsubst-on-templates.sh
# /docker-entrypoint.sh: Configuration complete; ready <span class="hljs-keyword">for</span> start up
</code></pre><p>The command is pretty self-explanatory. The only portion that may require some explanation is the <code>--publish 8080:80</code> portion which will be explained in the next sub-section.</p>
<h3 id="heading-how-to-publish-a-port">How to Publish a Port</h3>
<p>Containers are isolated environments. Your host system doesn't know anything about what's going on inside a container. Hence, applications running inside a container remain inaccessible from the outside.</p>
<p>To allow access from outside of a container, you must publish the appropriate port inside the container to a port on your local network. The common syntax for the <code>--publish</code> or <code>-p</code> option is as follows:</p>
<pre><code>--publish &lt;host port&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">container</span> <span class="hljs-attr">port</span>&gt;</span></span>
</code></pre><p>When you wrote <code>--publish 8080:80</code> in the previous sub-section, it meant any request sent to port 8080 of your host system will be forwarded to port 80 inside the container‌.</p>
<p>Now to access the application on your browser, visit <code>http://127.0.0.1:8080</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can stop the container by simply hitting the <code>ctrl + c</code> key combination while the terminal window is in focus or closing off the terminal window completely.</p>
<h3 id="heading-how-to-use-detached-mode">How to Use Detached Mode</h3>
<p>Another very popular option of the <code>run</code> command is the <code>--detach</code> or <code>-d</code> option. In the example above, in order for the container to keep running, you had to keep the terminal window open. Closing the terminal window also stopped the running container.</p>
<p>This is because, by default, containers run in the foreground and attach themselves to the terminal like any other normal program invoked from the terminal. </p>
<p>In order to override this behavior and keep a container running in background, you can include the <code>--detach</code> option with the <code>run</code> command as follows:</p>
<pre><code>docker container run --detach --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> fhsinchy/hello-dock

# <span class="hljs-number">9</span>f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc
</code></pre><p>Unlike the previous example, you won't get a wall of text thrown at you this time. Instead what you'll get is the ID of the newly created container.</p>
<p>The order of the options you provide doesn't really matter. If you put the <code>--publish</code> option before the <code>--detach</code> option, it'll work just the same. One thing that you have to keep in mind in case of the <code>run</code> command is that the image name must come last. If you put anything after the image name then that'll be passed as an argument to the container entry-point (explained in the <a class="post-section-overview" href="#executing-commands-inside-a-container">Executing Commands Inside a Container</a> sub-section) and may result in unexpected situations.</p>
<h3 id="heading-how-to-list-containers">How to List Containers</h3>
<p>The <code>container ls</code> command can be used to list out containers that are currently running. To do so execute following command:</p>
<pre><code>docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">5</span> seconds ago       Up <span class="hljs-number">5</span> seconds        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   gifted_sammet
</code></pre><p>A container named <code>gifted_sammet</code> is running. It was created <code>5 seconds ago</code> and the status is <code>Up 5 seconds,</code> which indicates that the container has been running fine since its creation.</p>
<p>The <code>CONTAINER ID</code> is <code>9f21cb777058</code> which is the first 12 characters of the full container ID. The full container ID is <code>9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc</code> which is 64 characters long. This full container ID was printed as the output of the <code>docker container run</code> command in the previous section.</p>
<p>Listed under the <code>PORTS</code> column, port 8080 from your local network is pointing towards port 80 inside the container. The name <code>gifted_sammet</code> is generated by Docker and can be something completely different in your computer.</p>
<p>The <code>container ls</code> command only lists the containers that are currently running on your system. In order to list out the containers that have run in the past you can use the <code>--all</code> or <code>-a</code> option.</p>
<pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                     PORTS                  NAMES
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">2</span> minutes ago       Up <span class="hljs-number">2</span> minutes               <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   gifted_sammet
# <span class="hljs-number">6</span>cf52771dde1        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">3</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">3</span> minutes ago                          reverent_torvalds
# <span class="hljs-number">128</span>ec8ceab71        hello-world           <span class="hljs-string">"/hello"</span>                 <span class="hljs-number">4</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">4</span> minutes ago                          exciting_chebyshev
</code></pre><p>As you can see, the second container in the list <code>reverent_torvalds</code> was created earlier and has exited with the status code 0, which indicates that no error was produced during the runtime of the container.</p>
<h3 id="heading-how-to-name-or-rename-a-container">How to Name or Rename a Container</h3>
<p>By default, every container has two identifiers. They are as follows:</p>
<ul>
<li><code>CONTAINER ID</code> - a random 64 character-long string.</li>
<li><code>NAME</code> - combination of two random words, joined with an underscore.</li>
</ul>
<p>Referring to a container based on these two random identifiers is kind of inconvenient. It would be great if the containers could be referred to using a name defined by you.</p>
<p>Naming a container can be achieved using the <code>--name</code> option. To run another container using the <code>fhsinchy/hello-dock</code> image with the name <code>hello-dock-container</code> you can execute the following command:</p>
<pre><code>docker container run --detach --publish <span class="hljs-number">8888</span>:<span class="hljs-number">80</span> --name hello-dock-container fhsinchy/hello-dock

# b1db06e400c4c5e81a93a64d30acc1bf821bed63af36cab5cdb95d25e114f5fb
</code></pre><p>The 8080 port on local network is occupied by the <code>gifted_sammet</code> container (the container created in the previous sub-section). That's why you'll have to use a different port number, like 8888. Now to verify, run the <code>container ls</code> command:</p>
<pre><code>docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">28</span> seconds ago      Up <span class="hljs-number">26</span> seconds       <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8888</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">4</span> minutes ago       Up <span class="hljs-number">4</span> minutes        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   gifted_sammet
</code></pre><p>A new container with the name of <code>hello-dock-container</code> has been started.</p>
<p>You can even rename old containers using the <code>container rename</code> command. Syntax for the command is as follows:</p>
<pre><code>docker container rename &lt;container identifier&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">new</span> <span class="hljs-attr">name</span>&gt;</span></span>
</code></pre><p>To rename the <code>gifted_sammet</code> container to <code>hello-dock-container-2</code>, execute following command:</p>
<pre><code>docker container rename gifted_sammet hello-dock-container<span class="hljs-number">-2</span>
</code></pre><p>The command doesn't yield any output but you can verify that the changes have taken place using the <code>container ls</code> command. The <code>rename</code> command works for containers both in running state and stopped state.</p>
<h3 id="heading-how-to-stop-or-kill-a-running-container">How to Stop or Kill a Running Container</h3>
<p>Containers running in the foreground can be stopped by simply closing the terminal window or hitting <code>ctrl + c</code>. Containers running in the background, however, can not be stopped in the same way.</p>
<p>There are two commands that deal with this task. The first one is the <code>container stop</code> command. Generic syntax for the command is as follows:</p>
<pre><code>docker container stop &lt;container identifier&gt;
</code></pre><p>Where <code>container identifier</code> can either be the id or the name of the container. </p>
<p>I hope that you remember the container you started in the previous section. It's still running in the background. Get the identifier for that container using <code>docker container ls</code> (I'll be using <code>hello-dock-container</code> container for this demo). Now execute the following command to stop the container:</p>
<pre><code>docker container stop hello-dock-container

# hello-dock-container
</code></pre><p>If you use the name as identifier, you'll get the name thrown back to you as output. The <code>stop</code> command shuts down a container gracefully by sending a <code>SIGTERM</code> signal. If the container doesn't stop within a certain period, a <code>SIGKILL</code> signal is sent which shuts down the container immediately.</p>
<p>In cases where you want to send a <code>SIGKILL</code> signal instead of a <code>SIGTERM</code> signal, you may use the <code>container kill</code> command instead. The <code>container kill</code> command follows the same syntax as the <code>stop</code> command.</p>
<pre><code>docker container kill hello-dock-container<span class="hljs-number">-2</span>

# hello-dock-container<span class="hljs-number">-2</span>
</code></pre><h3 id="heading-how-to-restart-a-container">How to Restart a Container</h3>
<p>When I say restart I mean two scenarios specifically. They are as follows:</p>
<ul>
<li>Restarting a container that has been previously stopped or killed.</li>
<li>Rebooting a running container.</li>
</ul>
<p>As you've already learned from a previous sub-section, stopped containers remain in your system. If you want you can restart them. The <code>container start</code> command can be used to start any stopped or killed container. The syntax of the command is as follows:</p>
<pre><code>docker container start &lt;container identifier&gt;
</code></pre><p>You can get the list of all containers by executing the <code>container ls --all</code> command. Then look for the containers with <code>Exited</code> status.</p>
<pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                        PORTS               NAMES
# b1db06e400c4        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">3</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">47</span> seconds ago                         hello-dock-container
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">7</span> minutes ago       Exited (<span class="hljs-number">137</span>) <span class="hljs-number">17</span> seconds ago                       hello-dock-container<span class="hljs-number">-2</span>
# <span class="hljs-number">6</span>cf52771dde1        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">7</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">7</span> minutes ago                          reverent_torvalds
# <span class="hljs-number">128</span>ec8ceab71        hello-world           <span class="hljs-string">"/hello"</span>                 <span class="hljs-number">9</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">9</span> minutes ago                          exciting_chebyshev
</code></pre><p>Now to restart the <code>hello-dock-container</code> container, you may execute the following command:</p>
<pre><code>docker container start hello-dock-container

# hello-dock-container
</code></pre><p>Now you can ensure that the container is running by looking at the list of running containers using the <code>container ls</code> command. </p>
<p>The <code>container start</code> command starts any container in detached mode by default and retains any port configurations made previously. So if you visit <code>http://127.0.0.1:8080</code> now, you should be able to access the <code>hello-dock</code> application just like before.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, in scenarios where you would like to reboot a running container you may use the <code>container restart</code> command. The <code>container restart</code> command follows the exact syntax as the <code>container start</code> command.</p>
<pre><code>docker container restart hello-dock-container<span class="hljs-number">-2</span>

# hello-dock-container<span class="hljs-number">-2</span>
</code></pre><p>The main difference between the two commands is that the <code>container restart</code> command attempts to stop the target container and then starts it back up again, whereas the start command just starts an already stopped container.</p>
<p>In case of a stopped container, both commands are exactly the same. But in case of a running container, you must use the <code>container restart</code> command.</p>
<h3 id="heading-how-to-create-a-container-without-running">How to Create a Container Without Running</h3>
<p>So far in this section, you've started containers using the <code>container run</code> command which is in reality a combination of two separate commands. These commands are as follows:</p>
<ul>
<li><code>container create</code> command creates a container from a given image.</li>
<li><code>container start</code> command starts a container that has been already created.</li>
</ul>
<p>Now, to perform the demonstration shown in the "How to Run a Container" section using these two commands, you can do something like the following:</p>
<pre><code>docker container create --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> fhsinchy/hello-dock

# <span class="hljs-number">2e7</span>ef5098bab92f4536eb9a372d9b99ed852a9a816c341127399f51a6d053856

docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
# <span class="hljs-number">2e7</span>ef5098bab        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">30</span> seconds ago      Created                                 hello-dock
</code></pre><p>Evident by the output of the <code>container ls --all</code> command, a container with the name of <code>hello-dock</code> has been created using the <code>fhsinchy/hello-dock</code> image. The <code>STATUS</code> of the container is <code>Created</code> at the moment, and, given that it's not running, it won't be listed without the use of the <code>--all</code> option. </p>
<p>Once the container has been created, it can be started using the <code>container start</code> command.</p>
<pre><code>docker container start hello-dock

# hello-dock

docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED              STATUS              PORTS                  NAMES
# <span class="hljs-number">2e7</span>ef5098bab        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   About a minute ago   Up <span class="hljs-number">29</span> seconds       <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock
</code></pre><p>The container <code>STATUS</code> has changed from <code>Created</code> to <code>Up 29 seconds</code> which indicates that the container is now in running state. The port configuration has also shown up in the <code>PORTS</code> column which was previously empty.‌</p>
<p>Although you can get away with the <code>container run</code> command for the majority of the scenarios, there will be some situations later on in the book that require you to use this <code>container create</code> command.</p>
<h3 id="heading-how-to-remove-dangling-containers">How to Remove Dangling Containers</h3>
<p>As you've already seen, containers that have been stopped or killed remain in the system. These dangling containers can take up space or can conflict with newer containers.</p>
<p>In order to remove a stopped container you can use the <code>container rm</code> command. The generic syntax is as follows:</p>
<pre><code>docker container rm &lt;container identifier&gt;
</code></pre><p>To find out which containers are not running, use the <code>container ls --all</code> command and look for containers with <code>Exited</code> status.</p>
<pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                      PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">6</span> minutes ago       Up About a minute           <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8888</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">10</span> minutes ago      Up About a minute           <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container<span class="hljs-number">-2</span>
# <span class="hljs-number">6</span>cf52771dde1        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">10</span> minutes ago      Exited (<span class="hljs-number">0</span>) <span class="hljs-number">10</span> minutes ago                          reverent_torvalds
# <span class="hljs-number">128</span>ec8ceab71        hello-world           <span class="hljs-string">"/hello"</span>                 <span class="hljs-number">12</span> minutes ago      Exited (<span class="hljs-number">0</span>) <span class="hljs-number">12</span> minutes ago                          exciting_chebyshev
</code></pre><p>As can be seen in the output, the containers with ID <code>6cf52771dde1</code> and <code>128ec8ceab71</code> are not running. To remove the <code>6cf52771dde1</code> you can execute the following command:</p>
<pre><code>docker container rm <span class="hljs-number">6</span>cf52771dde1

# <span class="hljs-number">6</span>cf52771dde1
</code></pre><p>You can check if the container was deleted or not by using the <code>container ls</code> command. You can also remove multiple containers at once by passing their identifiers one after another separated by spaces.</p>
<p>Or, instead of removing individual containers, if you want to remove all dangling containers at one go, you can use the <code>container prune</code> command.</p>
<p>You can check the container list using the <code>container ls --all</code> command to make sure that the dangling containers have been removed:</p>
<pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">8</span> minutes ago       Up <span class="hljs-number">3</span> minutes        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8888</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">12</span> minutes ago      Up <span class="hljs-number">3</span> minutes        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container<span class="hljs-number">-2</span>
</code></pre><p>If you are following the book exactly as written so far, you should only see the <code>hello-dock-container</code> and <code>hello-dock-container-2</code> in the list. I would suggest stopping and removing both containers before going on to the next section.</p>
<p>There is also the <code>--rm</code> option for the <code>container run</code>  and <code>container start</code> commands which indicates that you want the containers removed as soon as they're stopped. To start another <code>hello-dock</code> container with the <code>--rm</code> option, execute the following command:</p>
<pre><code>docker container run --rm --detach --publish <span class="hljs-number">8888</span>:<span class="hljs-number">80</span> --name hello-dock-volatile fhsinchy/hello-dock

# <span class="hljs-number">0</span>d74e14091dc6262732bee226d95702c21894678efb4043663f7911c53fb79f3
</code></pre><p>You can use the <code>container ls</code> command to verify that the container is running:</p>
<pre><code>docker container ls

# CONTAINER ID   IMAGE                 COMMAND                  CREATED              STATUS              PORTS                  NAMES
# <span class="hljs-number">0</span>d74e14091dc   fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   About a minute ago   Up About a minute   <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8888</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-volatile
</code></pre><p>Now if you stop the container and then check again with the <code>container ls --all</code> command:</p>
<pre><code>docker container stop hello-dock-volatile

# hello-dock-volatile

docker container ls --all

# CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
</code></pre><p>The container has been removed automatically. From now on I'll use the <code>--rm</code> option for most of the containers. I'll explicitly mention where it's not needed.</p>
<h3 id="heading-how-to-run-a-container-in-interactive-mode">How to Run a Container in Interactive Mode</h3>
<p>So far you've only run containers created from either the <a target="_blank" href="https://hub.docker.com/_/hello-world">hello-world</a> image or the <a target="_blank" href="https://hub.docker.com/r/fhsinchy/hello-dock">fhsinchy/hello-dock</a> image. These images are made for executing simple programs that are not interactive.</p>
<p>Well, all images are not that simple. Images can encapsulate an entire Linux distribution inside them. </p>
<p>Popular distributions such as <a target="_blank" href="https://ubuntu.com/">Ubuntu</a>, <a target="_blank" href="https://fedora.org/">Fedora</a>, and <a target="_blank" href="https://debian.org/">Debian</a> all have official Docker images available in the hub. Programming languages such as <a target="_blank" href="https://hub.docker.com/_/python">python</a>, <a target="_blank" href="https://hub.docker.com/_/php">php</a>, <a target="_blank" href="https://hub.docker.com/_/golang">go</a> or run-times like <a target="_blank" href="https://hub.docker.com/_/node">node</a> and <a target="_blank" href="https://hub.docker.com/r/hayd/deno">deno</a> all have their official images.</p>
<p>These images do not just run some pre-configured program. These are instead configured to run a shell by default. In case of the operating system images it can be something like <code>sh</code> or <code>bash</code> and in case of the programming languages or run-times, it is usually their default language shell.</p>
<p>As you may have already learned from your previous experiences with computers, shells are interactive programs. An image configured to run such a program is an interactive image. These images require a special <code>-it</code> option to be passed in the <code>container run</code> command.</p>
<p>As an example, if you run a container using the <code>ubuntu</code> image by executing <code>docker container run ubuntu</code> you'll see nothing happens. But if you execute the same command with the <code>-it</code> option, you should land directly on bash inside the Ubuntu container.</p>
<pre><code>docker container run --rm -it ubuntu

# root@dbb1f56b9563:<span class="hljs-regexp">/# cat /</span>etc/os-release
# NAME=<span class="hljs-string">"Ubuntu"</span>
# VERSION=<span class="hljs-string">"20.04.1 LTS (Focal Fossa)"</span>
# ID=ubuntu
# ID_LIKE=debian
# PRETTY_NAME=<span class="hljs-string">"Ubuntu 20.04.1 LTS"</span>
# VERSION_ID=<span class="hljs-string">"20.04"</span>
# HOME_URL=<span class="hljs-string">"https://www.ubuntu.com/"</span>
# SUPPORT_URL=<span class="hljs-string">"https://help.ubuntu.com/"</span>
# BUG_REPORT_URL=<span class="hljs-string">"https://bugs.launchpad.net/ubuntu/"</span>
# PRIVACY_POLICY_URL=<span class="hljs-string">"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"</span>
# VERSION_CODENAME=focal
# UBUNTU_CODENAME=focal
</code></pre><p>As you can see from the output of the <code>cat /etc/os-release</code> command, I am indeed interacting with the bash running inside the Ubuntu container.</p>
<p>The <code>-it</code> option sets the stage for you to interact with any interactive program inside a container. This option is actually two separate options mashed together.</p>
<ul>
<li>The <code>-i</code> or <code>--interactive</code> option connects you to the input stream of the container, so that you can send inputs to bash.</li>
<li>The <code>-t</code> or <code>--tty</code> option makes sure that you get some good formatting and a native terminal-like experience by allocating a pseudo-tty.</li>
</ul>
<p>You need to use the <code>-it</code> option whenever you want to run a container in interactive mode. Another example can be running the <code>node</code> image as follows:</p>
<pre><code>docker container run -it node

# Welcome to Node.js v15<span class="hljs-number">.0</span><span class="hljs-number">.0</span>.
# Type <span class="hljs-string">".help"</span> <span class="hljs-keyword">for</span> more information.
# &gt; [<span class="hljs-string">'farhan'</span>, <span class="hljs-string">'hasin'</span>, <span class="hljs-string">'chowdhury'</span>].map(<span class="hljs-function"><span class="hljs-params">name</span> =&gt;</span> name.toUpperCase())
# [ <span class="hljs-string">'FARHAN'</span>, <span class="hljs-string">'HASIN'</span>, <span class="hljs-string">'CHOWDHURY'</span> ]
</code></pre><p>Any valid JavaScript code can be executed in the node shell. Instead of writing <code>-it</code> you can be more verbose by writing <code>--interactive --tty</code> separately.</p>
<h3 id="heading-how-to-execute-commands-inside-a-container">How to Execute Commands Inside a Container</h3>
<p>In the <a target="_blank" href="https://www.freecodecamp.org/news/@fhsinchy/s/the-docker-handbook/~/drafts/-MS1b3opwENd_9qH1jTO/hello-world-in-docker">Hello World in Docker</a> section of this book, you've seen me executing a command inside an Alpine Linux container. It went something like this:</p>
<pre><code>docker run alpine uname -a
# Linux f08dbbe9199b <span class="hljs-number">5.8</span><span class="hljs-number">.0</span><span class="hljs-number">-22</span>-generic #<span class="hljs-number">23</span>-Ubuntu SMP Fri Oct <span class="hljs-number">9</span> <span class="hljs-number">00</span>:<span class="hljs-number">34</span>:<span class="hljs-number">40</span> UTC <span class="hljs-number">2020</span> x86_64 Linux
</code></pre><p>In this command, I've executed the <code>uname -a</code> command inside an Alpine Linux container. Scenarios like this (where all you want to do is to execute a certain command inside a certain container) are pretty common.</p>
<p>Assume that you want encode a string using the <code>base64</code> program. This is something that's available in almost any Linux or Unix based operating system (but not on Windows). </p>
<p>In this situation you can quickly spin up a container using images like <a target="_blank" href="https://hub.docker.com/_/busybox">busybox</a> and let it do the job.</p>
<p>The generic syntax for encoding a string using <code>base64</code> is as follows:</p>
<pre><code>echo -n my-secret | base64

# bXktc2VjcmV0
</code></pre><p>And the generic syntax for passing a command to a container that is not running is as follows:</p>
<pre><code>docker container run &lt;image name&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">command</span>&gt;</span></span>
</code></pre><p>To perform the base64 encoding using the busybox image, you can execute the following command:</p>
<pre><code>docker container run --rm busybox sh -c <span class="hljs-string">"echo -n my-secret | base64

# bXktc2VjcmV0</span>
</code></pre><p>What happens here is that, in a <code>container run</code> command, whatever you pass after the image name gets passed to the default entry point of the image. </p>
<p>An entry point is like a gateway to the image. Most of the images except the executable images (explained in the <a target="_blank" href="https://www.freecodecamp.org/news/@fhsinchy/s/the-docker-handbook/~/drafts/-MS1b3opwENd_9qH1jTO/container-manipulation-basics#working-with-executable-images">Working With Executable Images</a> sub-section) use shell or <code>sh</code> as the default entry-point. So any valid shell command can be passed to them as arguments.</p>
<h3 id="heading-how-to-work-with-executable-images">How to Work With Executable Images</h3>
<p>In the previous section, I briefly mentioned executable images. These images are designed to behave like executable programs.</p>
<p>Take for example my <a target="_blank" href="https://github.com/fhsinchy/rmbyext">rmbyext</a> project. This is a simple Python script capable of recursively deleting files of given extensions. To learn more about the project, you can checkout the repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/fhsinchy/rmbyext">https://github.com/fhsinchy/rmbyext</a></div>
<p>If you have both Git and Python installed, you can install this script by executing the following command:</p>
<pre><code>pip install git+https:<span class="hljs-comment">//github.com/fhsinchy/rmbyext.git#egg=rmbyext</span>
</code></pre><p>Assuming Python has been set up properly on your system, the script should be available anywhere through the terminal. The generic syntax for using this script is as follows:</p>
<pre><code>rmbyext &lt;file extension&gt;
</code></pre><p>To test it out, open up your terminal inside an empty directory and create some files in it with different extensions. You can use the <code>touch</code> command to do so. Now, I have a directory on my computer with the following files:</p>
<pre><code>touch a.pdf b.pdf c.txt d.pdf e.txt

ls

# a.pdf  b.pdf  c.txt  d.pdf  e.txt
</code></pre><p>To delete all the <code>pdf</code> files from this directory, you can execute the following command:</p>
<pre><code>rmbyext pdf

# Removing: PDF
# b.pdf
# a.pdf
# d.pdf
</code></pre><p>An executable image for this program should be able to take extensions of files as arguments and delete them just like the <code>rmbyext</code> program did.</p>
<p>The <a target="_blank" href="https://hub.docker.com/r/fhsinchy/rmbyext">fhsinchy/rmbyext</a> image behaves in a similar manner. This image contains a copy of the <code>rmbyext</code> script and is configured to run the script on a directory <code>/zone</code> inside the container.</p>
<p>Now the problem is that containers are isolated from your local system, so the <code>rmbyext</code> program running inside the container doesn't have any access to your local file system. So, if somehow you can map the local directory containing the <code>pdf</code> files to the <code>/zone</code> directory inside the container, the files should be accessible to the container.</p>
<p>One way to grant a container direct access to your local file system is by using <a target="_blank" href="https://docs.docker.com/storage/bind-mounts/">bind mounts</a>. </p>
<p>A bind mount lets you form a two way data binding between the content of a local file system directory (source) and another directory inside a container (destination). This way any changes made in the destination directory will take effect on the source directory and vise versa.</p>
<p>Let's see a bind mount in action. To delete files using this image instead of the program itself, you can execute the following command:</p>
<pre><code>docker container run --rm -v $(pwd):<span class="hljs-regexp">/zone fhsinchy/</span>rmbyext pdf

# Removing: PDF
# b.pdf
# a.pdf
# d.pdf
</code></pre><p>As you may have already guessed by seeing the <code>-v $(pwd):/zone</code> part in the command, the  <code>-v</code> or <code>--volume</code> option is used for creating a bind mount for a container. This option can take three fields separated by colons (<code>:</code>). The generic syntax for the option is as follows:</p>
<pre><code>--volume &lt;local file system directory absolute path&gt;:&lt;container file system directory absolute path&gt;:&lt;read write access&gt;
</code></pre><p>The third field is optional but you must pass the absolute path of your local directory and the absolute path of the directory inside the container. </p>
<p>The source directory in my case is <code>/home/fhsinchy/the-zone</code>. Given that my terminal is opened inside the directory, <code>$(pwd)</code> will be replaced with <code>/home/fhsinchy/the-zone</code> which contains the previously mentioned <code>.pdf</code> and <code>.txt</code> files. </p>
<p>You can learn more about <a target="_blank" href="https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html">command substitution here</a> if you want to.</p>
<p>The <code>--volume</code> or <code>-v</code> option is valid for the <code>container run</code> as well as the <code>container create</code> commands. We'll explore volumes in greater detail in the upcoming sections so don't worry if you didn't understand them very well here.</p>
<p>The difference between a regular image and an executable one is that the entry-point for an executable image is set to a custom program instead of <code>sh</code>, in this case the <code>rmbyext</code> program. And as you've learned in the previous sub-section, anything you write after the image name in a <code>container run</code> command gets passed to the entry-point of the image.</p>
<p>So in the end the <code>docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf</code> command translates to <code>rmbyext pdf</code> inside the container. Executable images are not that common in the wild but can be very useful in certain cases.</p>
<h2 id="heading-docker-image-manipulation-basics">Docker Image Manipulation Basics</h2>
<p>Now that you have a solid understanding of how to run containers using publicly available images, it's time for you to learn about creating your very own images.</p>
<p>In this section, you'll learn the fundamentals of creating images, running containers using them, and sharing them online.</p>
<p>I would suggest you to install <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> with the official <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker">Docker Extension</a> from the marketplace. This will greatly help your development experience.</p>
<h3 id="heading-how-to-create-a-docker-image">How to Create a Docker Image</h3>
<p>As I've already explained in the <a class="post-section-overview" href="#image">Hello World in Docker</a> section, images are multi-layered self-contained files that act as the template for creating Docker containers. They are like a frozen, read-only copy of a container.</p>
<p>In order to create an image using one of your programs you must have a clear vision of what you want from the image. Take the official <a target="_blank" href="https://hub.docker.com/_/nginx">nginx</a> image, for example. You can start a container using this image simply by executing the following command:</p>
<pre><code>docker container run --rm --detach --name <span class="hljs-keyword">default</span>-nginx --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> nginx

# b379ecd5b6b9ae27c144e4fa12bdc5d0635543666f75c14039eea8d5f38e3f56

docker container ls

# CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b379ecd5b6b9        nginx               <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">8</span> seconds ago       Up <span class="hljs-number">8</span> seconds        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   <span class="hljs-keyword">default</span>-nginx
</code></pre><p>Now, if you visit <code>http://127.0.0.1:8080</code> in the browser, you'll see a default response page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>That's all nice and good, but what if you want to make a custom NGINX image which functions exactly like the official one, but that's built by you? That's a completely valid scenario to be honest. In fact, let's do that.‌</p>
<p>In order to make a custom NGINX image, you must have a clear picture of what the final state of the image will be. In my opinion the image should be as follows:</p>
<ul>
<li>The image should have NGINX pre-installed which can be done using a package manager or can be built from source.</li>
<li>The image should start NGINX automatically upon running.</li>
</ul>
<p>That's simple. If you've cloned the project repository linked in this book, go inside the project root and look for a directory named <code>custom-nginx</code> in there. </p>
<p>Now, create a new file named <code>Dockerfile</code> inside that directory. A <code>Dockerfile</code> is a collection of instructions that, once processed by the daemon, results in an image. Content for the <code>Dockerfile</code> is as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install nginx -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>Images are multi-layered files and in this file, each line (known as instructions) that you've written creates a layer for your image.</p>
<ul>
<li>Every valid <code>Dockerfile</code> starts with a <code>FROM</code> instruction. This instruction sets the base image for your resultant image. By setting <code>ubuntu:latest</code> as the base image here, you get all the goodness of Ubuntu already available in your custom image, so you can use things like the <code>apt-get</code> command for easy package installation.</li>
<li>The <code>EXPOSE</code> instruction is used to indicate the port that needs to be published. Using this instruction doesn't mean that you won't need to <code>--publish</code> the port. You'll still need to use the <code>--publish</code> option explicitly. This <code>EXPOSE</code> instruction works like a documentation for someone who's trying to run a container using your image. It also has some other uses that I won't be discussing here.</li>
<li>The <code>RUN</code> instruction in a <code>Dockerfile</code> executes a command inside the container shell. The <code>apt-get update &amp;&amp; apt-get install nginx -y</code> command checks for updated package versions and installs NGINX. The <code>apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</code> command is used for clearing the package cache because you don't want any unnecessary baggage in your image. These two commands are simple Ubuntu stuff, nothing fancy. The <code>RUN</code> instructions here are written in <code>shell</code> form. These can also be written in <code>exec</code> form. You can consult the <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#run">official reference</a> for more information.</li>
<li>Finally the <code>CMD</code> instruction sets the default command for your image. This instruction is written in <code>exec</code> form here comprising of three separate parts. Here, <code>nginx</code> refers to the NGINX executable. The <code>-g</code> and <code>daemon off</code> are options for NGINX. Running NGINX as a single process inside containers is considered a best practice hence the usage of this option. The <code>CMD</code> instruction can also be written in <code>shell</code> form. You can consult the <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#cmd">official reference</a> for more information.</li>
</ul>
<p>Now that you have a valid <code>Dockerfile</code> you can build an image out of it. Just like the container related commands, the image related commands can be issued using the following syntax:</p>
<pre><code>docker image &lt;command&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">options</span>&gt;</span></span>
</code></pre><p>To build an image using the <code>Dockerfile</code> you just wrote, open up your terminal inside the <code>custom-nginx</code> directory and execute the following command:</p>
<pre><code>docker image build .

# Sending build context to Docker daemon  <span class="hljs-number">3.584</span>kB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">4</span> : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step <span class="hljs-number">2</span>/<span class="hljs-number">4</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">9</span>eae86582ec7
# Removing intermediate container <span class="hljs-number">9</span>eae86582ec7
#  ---&gt; <span class="hljs-number">8235</span>bd799a56
# Step <span class="hljs-number">3</span>/<span class="hljs-number">4</span> : RUN apt-get update &amp;&amp;     apt-get install nginx -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; Running in a44725cbb3fa
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container a44725cbb3fa
#  ---&gt; 3066bd20292d
# Step 4/4 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 4792e4691660
# Removing intermediate container 4792e4691660
#  ---&gt; 3199372aa3fc
# Successfully built 3199372aa3fc</span>
</code></pre><p>To perform an image build, the daemon needs two very specific pieces of information. These are the name of the <code>Dockerfile</code> and the build context. In the command issued above:</p>
<ul>
<li><code>docker image build</code> is the command for building the image. The daemon finds any file named <code>Dockerfile</code> within the context.</li>
<li>The <code>.</code> at the end sets the context for this build. The context means the directory accessible by the daemon during the build process.</li>
</ul>
<p>Now to run a container using this image, you can use the <code>container run</code> command coupled with the image ID that you received as the result of the build process. In my case the id is <code>3199372aa3fc</code> evident by the <code>Successfully built 3199372aa3fc</code> line in the previous code block.</p>
<pre><code>docker container run --rm --detach --name custom-nginx-packaged --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> <span class="hljs-number">3199372</span>aa3fc

# ec09d4e1f70c903c3b954c8d7958421cdd1ae3d079b57f929e44131fbf8069a0

docker container ls

# CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
# ec09d4e1f70c        <span class="hljs-number">3199372</span>aa3fc        <span class="hljs-string">"nginx -g 'daemon of…"</span>   <span class="hljs-number">23</span> seconds ago      Up <span class="hljs-number">22</span> seconds       <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   custom-nginx-packaged
</code></pre><p>To verify, visit <code>http://127.0.0.1:8080</code> and you should see the default response page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-tag-docker-images">How to Tag Docker Images</h3>
<p>Just like containers, you can assign custom identifiers to your images instead of relying on the randomly generated ID. In case of an image, it's called tagging instead of naming. The <code>--tag</code> or <code>-t</code> option is used in such cases. </p>
<p>Generic syntax for the option is as follows:</p>
<pre><code>--tag &lt;image repository&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">image</span> <span class="hljs-attr">tag</span>&gt;</span></span>
</code></pre><p>The repository is usually known as the image name and the tag indicates a certain build or version. </p>
<p>Take the official <a target="_blank" href="https://hub.docker.com/_/mysql">mysql</a> image, for example. If you want to run a container using a specific version of MySQL, like 5.7, you can execute <code>docker container run mysql:5.7</code> where <code>mysql</code> is the image repository and <code>5.7</code> is the tag.</p>
<p>In order to tag your custom NGINX image with <code>custom-nginx:packaged</code> you can execute the following command:</p>
<pre><code>docker image build --tag custom-nginx:packaged .

# Sending build context to Docker daemon  <span class="hljs-number">1.055</span>MB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">4</span> : FROM ubuntu:latest
#  ---&gt; f63181f19b2f
# Step <span class="hljs-number">2</span>/<span class="hljs-number">4</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">53</span>ab370b9efc
# Removing intermediate container <span class="hljs-number">53</span>ab370b9efc
#  ---&gt; <span class="hljs-number">6</span>d6460a74447
# Step <span class="hljs-number">3</span>/<span class="hljs-number">4</span> : RUN apt-get update &amp;&amp;     apt-get install nginx -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; Running in b4951b6b48bb
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container b4951b6b48bb
#  ---&gt; fdc6cdd8925a
# Step 4/4 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 3bdbd2af4f0e
# Removing intermediate container 3bdbd2af4f0e
#  ---&gt; f8837621b99d
# Successfully built f8837621b99d
# Successfully tagged custom-nginx:packaged</span>
</code></pre><p>Nothing will change except the fact that you can now refer to your image as <code>custom-nginx:packaged</code> instead of some long random string.</p>
<p>In cases where you forgot to tag an image during build time, or maybe you want to change the tag, you can use the <code>image tag</code> command to do that:</p>
<pre><code>docker image tag &lt;image id&gt; &lt;image repository&gt;:&lt;image tag&gt;

## or ##

docker image tag &lt;image repository&gt;:&lt;image tag&gt; &lt;new image repository&gt;:&lt;new image tag&gt;
</code></pre><h3 id="heading-how-to-list-and-remove-docker-images">How to List and Remove Docker Images</h3>
<p>Just like the <code>container ls</code> command, you can use the <code>image ls</code> command to list all the images in your local system:</p>
<pre><code>docker image ls

# REPOSITORY     TAG        IMAGE ID       CREATED         SIZE
# &lt;none&gt;         <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">none</span>&gt;</span>     3199372aa3fc   7 seconds ago   132MB
# custom-nginx   packaged   f8837621b99d   4 minutes ago   132MB</span>
</code></pre><p>Images listed here can be deleted using the <code>image rm</code> command. The generic syntax is as follows:</p>
<pre><code>docker image rm &lt;image identifier&gt;
</code></pre><p>The identifier can be the image ID or image repository. If you use the repository, you'll have to identify the tag as well. To delete the <code>custom-nginx:packaged</code> image, you may execute the following command:</p>
<pre><code>docker image rm custom-nginx:packaged

# Untagged: custom-nginx:packaged
# Deleted: sha256:f8837621b99d3388a9e78d9ce49fbb773017f770eea80470fb85e0052beae242
# Deleted: sha256:fdc6cdd8925ac25b9e0ed1c8539f96ad89ba1b21793d061e2349b62dd517dadf
# Deleted: sha256:c20e4aa46615fe512a4133089a5cd66f9b7da76366c96548790d5bf865bd49c4
# Deleted: sha256:<span class="hljs-number">6</span>d6460a744475a357a2b631a4098aa1862d04510f3625feb316358536fcd8641
</code></pre><p>You can also use the <code>image prune</code> command to cleanup all un-tagged dangling images as follows:</p>
<pre><code>docker image prune --force

# Deleted Images:
# deleted: sha256:ba9558bdf2beda81b9acc652ce4931a85f0fc7f69dbc91b4efc4561ef7378aff
# deleted: sha256:ad9cc3ff27f0d192f8fa5fadebf813537e02e6ad472f6536847c4de183c02c81
# deleted: sha256:f1e9b82068d43c1bb04ff3e4f0085b9f8903a12b27196df7f1145aa9296c85e7
# deleted: sha256:ec16024aa036172544908ec4e5f842627d04ef99ee9b8d9aaa26b9c2a4b52baa

# Total reclaimed space: <span class="hljs-number">59.19</span>MB
</code></pre><p>The <code>--force</code> or <code>-f</code> option skips any confirmation questions. You can also use the <code>--all</code> or <code>-a</code> option to remove all cached images in your local registry.</p>
<h3 id="heading-how-to-understand-the-many-layers-of-a-docker-image">How to Understand the Many Layers of a Docker Image</h3>
<p>From the very beginning of this book, I've been saying that images are multi-layered files. In this sub-section I'll demonstrate the various layers of an image and how they play an important role in the build process of that image. </p>
<p>For this demonstration, I'll be using the <code>custom-nginx:packaged</code> image from the previous sub-section.</p>
<p>To visualize the many layers of an image, you can use the <code>image history</code> command. The various layers of the <code>custom-nginx:packaged</code> image can be visualized as follows:</p>
<pre><code>docker image history custom-nginx:packaged

# IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
# <span class="hljs-number">7</span>f16387f7307        <span class="hljs-number">5</span> minutes ago       /bin/sh -c #(nop)  CMD [<span class="hljs-string">"nginx"</span> <span class="hljs-string">"-g"</span> <span class="hljs-string">"daemon…   0B                             
# 587c805fe8df        5 minutes ago       /bin/sh -c apt-get update &amp;&amp;     apt-get ins…   60MB                
# 6fe4e51e35c1        6 minutes ago       /bin/sh -c #(nop)  EXPOSE 80                    0B                  
# d70eaf7277ea        17 hours ago        /bin/sh -c #(nop)  CMD ["</span>/bin/bash<span class="hljs-string">"]            0B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c mkdir -p /run/systemd &amp;&amp; echo 'do…   7B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c [ -z "</span>$(apt-get indextargets)<span class="hljs-string">" ]     0B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c set -xe   &amp;&amp; echo '#!/bin/sh' &gt; /…   811B                
# &lt;missing&gt;           17 hours ago        /bin/sh -c #(nop) ADD file:435d9776fdd3a1834…   72.9MB</span>
</code></pre><p>There are eight layers of this image. The upper most layer is the latest one and as you go down the layers get older. The upper most layer is the one that you usually use for running containers.</p>
<p>Now, let's have a closer look at the images beginning from image <code>d70eaf7277ea</code> down to <code>7f16387f7307</code>. I'll ignore the bottom four layers where the <code>IMAGE</code> is <code>&lt;missing&gt;</code> as they are not of our concern.</p>
<ul>
<li><code>d70eaf7277ea</code> was created by <code>/bin/sh -c #(nop)  CMD ["/bin/bash"]</code> which indicates that the default shell inside Ubuntu has been loaded successfully.</li>
<li><code>6fe4e51e35c1</code> was created by <code>/bin/sh -c #(nop)  EXPOSE 80</code> which was the second instruction in your code.</li>
<li><code>587c805fe8df</code> was created by <code>/bin/sh -c apt-get update &amp;&amp; apt-get install nginx -y &amp;&amp; apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</code> which was the third instruction in your code. You can also see that this image has a size of <code>60MB</code> given all necessary packages were installed during the execution of this instruction.</li>
<li>Finally the upper most layer <code>7f16387f7307</code> was created by <code>/bin/sh -c #(nop)  CMD ["nginx", "-g", "daemon off;"]</code> which sets the default command for this image.</li>
</ul>
<p>As you can see, the image comprises of many read-only layers, each recording a new set of changes to the state triggered by certain instructions. When you start a container using an image, you get a new writable layer on top of the other layers.</p>
<p>This layering phenomenon that happens every time you work with Docker has been made possible by an amazing technical concept called a union file system. Here, union means union in set theory. According to <a target="_blank" href="https://en.wikipedia.org/wiki/UnionFS">Wikipedia</a> - </p>
<blockquote>
<p>It allows files and directories of separate file systems, known as branches, to be transparently overlaid, forming a single coherent file system. Contents of directories which have the same path within the merged branches will be seen together in a single merged directory, within the new, virtual filesystem.</p>
</blockquote>
<p>By utilizing this concept, Docker can avoid data duplication and can use previously created layers as a cache for later builds. This results in compact, efficient images that can be used everywhere.</p>
<h3 id="heading-how-to-build-nginx-from-source">How to Build NGINX from Source</h3>
<p>In the previous sub-section, you learned about the <code>FROM</code>, <code>EXPOSE</code>, <code>RUN</code> and <code>CMD</code> instructions. In this sub-section you'll be learning a lot more about other instructions.</p>
<p>In this sub-section you'll again create a custom NGINX image. But the twist is that you'll be building NGINX from source instead of installing it using some package manager such as <code>apt-get</code> as in the previous example.</p>
<p>In order to build NGINX from source, you first need the source of NGINX. If you've cloned my projects repository you'll see a file named <code>nginx-1.19.2.tar.gz</code> inside the <code>custom-nginx</code> directory. You'll use this archive as the source for building NGINX.</p>
<p>Before diving into writing some code, let's plan out the process first. The image creation process this time can be done in seven steps. These are as follows:</p>
<ul>
<li>Get a good base image for building the application, like <a target="_blank" href="https://hub.docker.com/_/ubuntu">ubuntu</a>.</li>
<li>Install necessary build dependencies on the base image.</li>
<li>Copy the <code>nginx-1.19.2.tar.gz</code> file inside the image.</li>
<li>Extract the contents of the archive and get rid of it.</li>
<li>Configure the build, compile and install the program using the <code>make</code> tool.</li>
<li>Get rid of the extracted source code.</li>
<li>Run <code>nginx</code> executable.</li>
</ul>
<p>Now that you have a plan, let's begin by opening up old <code>Dockerfile</code> and updating its contents as follows:</p>
<pre><code>FROM ubuntu:latest

RUN apt-get update &amp;&amp; \
    apt-get install build-essential\ 
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1<span class="hljs-number">.1</span> \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*

COPY nginx-1.19.2.tar.gz .

RUN tar -xvf nginx-1.19.2.tar.gz &amp;&amp; rm nginx-1.19.2.tar.gz

RUN cd nginx-1.19.2 &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install

RUN rm -rf /nginx-1.19.2

CMD ["nginx", "-g", "daemon off;"]</span>
</code></pre><p>As you can see, the code inside the <code>Dockerfile</code> reflects the seven steps I talked about above.</p>
<ul>
<li>The <code>FROM</code> instruction sets Ubuntu as the base image making an ideal environment for building any application.</li>
<li>The <code>RUN</code> instruction installs standard packages necessary for building NGINX from source.</li>
<li>The <code>COPY</code> instruction here is something new. This instruction is responsible for copying the the <code>nginx-1.19.2.tar.gz</code> file inside the image. The generic syntax for the <code>COPY</code> instruction is <code>COPY &lt;source&gt; &lt;destination&gt;</code> where source is in your local filesystem and the destination is inside your image. The <code>.</code> as the destination means the working directory inside the image which is by default <code>/</code> unless set otherwise.</li>
<li>The second <code>RUN</code> instruction here extracts the contents from the archive using <code>tar</code> and gets rid of it afterwards.</li>
<li>The archive file contains a directory called <code>nginx-1.19.2</code> containing the source code. So on the next step, you'll have to <code>cd</code> inside that directory and perform the build process. You can read the <a target="_blank" href="https://itsfoss.com/install-software-from-source-code/">How to Install Software from Source Code… and Remove it Afterwards</a> article to learn more on the topic.</li>
<li>Once the build and installation is complete, you remove the <code>nginx-1.19.2</code> directory using <code>rm</code> command.</li>
<li>On the final step you start NGINX in single process mode just like you did before.</li>
</ul>
<p>Now to build an image using this code, execute the following command:</p>
<pre><code>docker image build --tag custom-nginx:built .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; Running in 2d0aa912ea47
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 2d0aa912ea47
#  ---&gt; cbe1ced3da11
# Step 3/7 : COPY nginx-1.19.2.tar.gz .
#  ---&gt; 7202902edf3f
# Step 4/7 : RUN tar -xvf nginx-1.19.2.tar.gz &amp;&amp; rm nginx-1.19.2.tar.gz
 ---&gt; Running in 4a4a95643020
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 4a4a95643020
#  ---&gt; f9dec072d6d6
# Step 5/7 : RUN cd nginx-1.19.2 &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in b07ba12f921e
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container b07ba12f921e
#  ---&gt; 5a877edafd8b
# Step 6/7 : RUN rm -rf /nginx-1.19.2
#  ---&gt; Running in 947e1d9ba828
# Removing intermediate container 947e1d9ba828
#  ---&gt; a7702dc7abb7
# Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 3110c7fdbd57
# Removing intermediate container 3110c7fdbd57
#  ---&gt; eae55f7369d3
# Successfully built eae55f7369d3
# Successfully tagged custom-nginx:built</span>
</code></pre><p>This code is alright but there are some places where we can make improvements.</p>
<ul>
<li>Instead of hard coding the filename like <code>nginx-1.19.2.tar.gz</code>, you can create an argument using the <code>ARG</code> instruction. This way, you'll be able to change the version or filename by just changing the argument.</li>
<li>Instead of downloading the archive manually, you can let the daemon download the file during the build process. There is another instruction like <code>COPY</code> called the <code>ADD</code> instruction which is capable of adding files from the internet.</li>
</ul>
<p>Open up the <code>Dockerfile</code> file and update its content as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install build-essential\ </span>
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.<span class="hljs-number">1</span> \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

<span class="hljs-keyword">ARG</span> FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
<span class="hljs-keyword">ARG</span> EXTENSION=<span class="hljs-string">"tar.gz"</span>

<span class="hljs-keyword">ADD</span><span class="bash"> https://nginx.org/download/<span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> tar -xvf <span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> &amp;&amp; rm <span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span></span>

<span class="hljs-keyword">RUN</span><span class="bash"> <span class="hljs-built_in">cd</span> <span class="hljs-variable">${FILENAME}</span> &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/<span class="hljs-built_in">log</span>/nginx/error.log \
        --http-log-path=/var/<span class="hljs-built_in">log</span>/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install</span>

<span class="hljs-keyword">RUN</span><span class="bash"> rm -rf /<span class="hljs-variable">${FILENAME}</span>}</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>The code is almost identical to the previous code block except for a new instruction called <code>ARG</code> on line 13, 14 and the usage of the <code>ADD</code> instruction on line 16. Explanation for the updated code is as follows:</p>
<ul>
<li>The <code>ARG</code> instruction lets you declare variables like in other languages. These variables or arguments can later be accessed using the <code>${argument name}</code> syntax. Here, I've put the filename <code>nginx-1.19.2</code> and the file extension <code>tar.gz</code> in two separate arguments. This way I can switch between newer versions of NGINX or the archive format by making a change in just one place. In the code above, I've added default values to the variables. Variable values can be passed as options of the <code>image build</code> command as well. You can consult the <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#arg">official reference</a> for more details.</li>
<li>In the <code>ADD</code> instruction, I've formed the download URL dynamically using the arguments declared above. The <code>https://nginx.org/download/${FILENAME}.${EXTENSION}</code> line will result in something like <code>https://nginx.org/download/nginx-1.19.2.tar.gz</code> during the build process. You can change the file version or the extension by changing it in just one place thanks to the <code>ARG</code> instruction.</li>
<li>The <code>ADD</code> instruction doesn't extract files obtained from the internet by default, hence the usage of <code>tar</code> on line 18.</li>
</ul>
<p>The rest of the code is almost unchanged. You should be able to understand the usage of the arguments by yourself now. Finally let's try to build an image from this updated code.</p>
<pre><code>docker image build --tag custom-nginx:built .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">9</span> : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step <span class="hljs-number">2</span>/<span class="hljs-number">9</span> : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; cbe1ced3da11
### LONG INSTALLATION STUFF GOES HERE ###
# Step 3/9 : ARG FILENAME="nginx-1.19.2"
#  ---&gt; Running in 33b62a0e9ffb
# Removing intermediate container 33b62a0e9ffb
#  ---&gt; fafc0aceb9c8
# Step 4/9 : ARG EXTENSION="tar.gz"
#  ---&gt; Running in 5c32eeb1bb11
# Removing intermediate container 5c32eeb1bb11
#  ---&gt; 36efdf6efacc
# Step 5/9 : ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .
# Downloading [==================================================&gt;]  1.049MB/1.049MB
#  ---&gt; dba252f8d609
# Step 6/9 : RUN tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION}
#  ---&gt; Running in 2f5b091b2125
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 2f5b091b2125
#  ---&gt; 2c9a325d74f1
# Step 7/9 : RUN cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in 11cc82dd5186
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container 11cc82dd5186
#  ---&gt; 6c122e485ec8
# Step 8/9 : RUN rm -rf /${FILENAME}}
#  ---&gt; Running in 04102366960b
# Removing intermediate container 04102366960b
#  ---&gt; 6bfa35420a73
# Step 9/9 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 63ee44b571bb
# Removing intermediate container 63ee44b571bb
#  ---&gt; 4ce79556db1b
# Successfully built 4ce79556db1b
# Successfully tagged custom-nginx:built</span>
</code></pre><p>Now you should be able to run a container using the <code>custom-nginx:built</code> image.</p>
<pre><code>docker container run --rm --detach --name custom-nginx-built --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> custom-nginx:built

# <span class="hljs-number">90</span>ccdbc0b598dddc4199451b2f30a942249d85a8ed21da3c8d14612f17eed0aa

docker container ls

# CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                  NAMES
# <span class="hljs-number">90</span>ccdbc0b598        custom-nginx:built   <span class="hljs-string">"nginx -g 'daemon of…"</span>   <span class="hljs-number">2</span> minutes ago       Up <span class="hljs-number">2</span> minutes        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   custom-nginx-built
</code></pre><p>A container using the <code>custom-nginx:built-v2</code> image has been successfully run. The container should be accessible at <code>http://127.0.0.1:8080</code> now.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And here is the trusty default response page from NGINX. You can visit the <a target="_blank" href="https://docs.docker.com/engine/reference/builder/">official reference</a> site to learn more about the available instructions.</p>
<h3 id="heading-how-to-optimize-docker-images">How to Optimize Docker Images</h3>
<p>The image we built in the last sub-section is functional but very unoptimized. To prove my point let's have a look at the size of the image using the <code>image ls</code> command:</p>
<pre><code>docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
# custom-nginx       built     <span class="hljs-number">1</span>f3aaf40bb54   <span class="hljs-number">16</span> minutes ago   <span class="hljs-number">343</span>MB
</code></pre><p>For an image containing only NGINX, that's too much. If you pull the official image and check its size, you'll see how small it is:</p>
<pre><code>docker image pull nginx:stable

# stable: Pulling <span class="hljs-keyword">from</span> library/nginx
# a076a628af6f: Pull complete 
# <span class="hljs-number">45</span>d7b5d3927d: Pull complete 
# <span class="hljs-number">5e326</span>fece82e: Pull complete 
# <span class="hljs-number">30</span>c386181b68: Pull complete 
# b15158e9ebbe: Pull complete 
# Digest: sha256:ebd0fd56eb30543a9195280eb81af2a9a8e6143496accd6a217c14b06acd1419
# Status: Downloaded newer image <span class="hljs-keyword">for</span> nginx:stable
# docker.io/library/nginx:stable

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
# custom-nginx       built     <span class="hljs-number">1</span>f3aaf40bb54   <span class="hljs-number">25</span> minutes ago   <span class="hljs-number">343</span>MB
# nginx              stable    b9e1dc12387a   <span class="hljs-number">11</span> days ago      <span class="hljs-number">133</span>MB
</code></pre><p>In order to find out the root cause, let's have a look at the <code>Dockerfile</code> first:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install build-essential\ </span>
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.<span class="hljs-number">1</span> \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

<span class="hljs-keyword">ARG</span> FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
<span class="hljs-keyword">ARG</span> EXTENSION=<span class="hljs-string">"tar.gz"</span>

<span class="hljs-keyword">ADD</span><span class="bash"> https://nginx.org/download/<span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> tar -xvf <span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> &amp;&amp; rm <span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span></span>

<span class="hljs-keyword">RUN</span><span class="bash"> <span class="hljs-built_in">cd</span> <span class="hljs-variable">${FILENAME}</span> &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/<span class="hljs-built_in">log</span>/nginx/error.log \
        --http-log-path=/var/<span class="hljs-built_in">log</span>/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install</span>

<span class="hljs-keyword">RUN</span><span class="bash"> rm -rf /<span class="hljs-variable">${FILENAME}</span>}</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>As you can see on line 3, the <code>RUN</code> instruction installs a lot of stuff. Although these packages are necessary for building NGINX from source, they are not necessary for running it. </p>
<p>Out of the 6 packages that we installed, only two are necessary for running NGINX. These are <code>libpcre3</code> and <code>zlib1g</code>. So a better idea would be to uninstall the other packages once the build process is done.</p>
<p>To do so, update your <code>Dockerfile</code> as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">ARG</span> FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
<span class="hljs-keyword">ARG</span> EXTENSION=<span class="hljs-string">"tar.gz"</span>

<span class="hljs-keyword">ADD</span><span class="bash"> https://nginx.org/download/<span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install build-essential \ </span>
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.<span class="hljs-number">1</span> \
                    libssl-dev \
                    -y &amp;&amp; \
    tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp; \
    cd ${FILENAME} &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/<span class="hljs-keyword">run</span><span class="bash">/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install &amp;&amp; \
    <span class="hljs-built_in">cd</span> / &amp;&amp; rm -rfv /<span class="hljs-variable">${FILENAME}</span> &amp;&amp; \
    apt-get remove build-essential \ </span>
                    libpcre3-dev \
                    zlib1g-dev \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get autoremove -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>As you can see, on line 10 a single <code>RUN</code> instruction is doing all the necessary heavy-lifting. The exact chain of events is as follows:</p>
<ul>
<li>From line 10 to line 17, all the necessary packages are being installed.</li>
<li>On line 18, the source code is being extracted and the downloaded archive gets removed.</li>
<li>From line 19 to line 28, NGINX is configured, built, and installed on the system.</li>
<li>On line 29, the extracted files from the downloaded archive get removed.</li>
<li>From line 30 to line 36, all the unnecessary packages are being uninstalled and cache cleared. The <code>libpcre3</code> and <code>zlib1g</code> packages are needed for running NGINX so we keep them.</li>
</ul>
<p>You may ask why am I doing so much work in a single <code>RUN</code> instruction instead of nicely splitting them into multiple instructions like we did previously. Well, splitting them up would be a mistake. </p>
<p>If you install packages and then remove them in separate <code>RUN</code> instructions, they'll live in separate layers of the image. Although the final image will not have the removed packages, their size will still be added to the final image since they exist in one of the layers consisting the image. So make sure you make these kind of changes on a single layer.</p>
<p>Let's build an image using this <code>Dockerfile</code> and see the differences.</p>
<pre><code>docker image build --tag custom-nginx:built .

# Sending build context to Docker daemon  <span class="hljs-number">1.057</span>MB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM ubuntu:latest
#  ---&gt; f63181f19b2f
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">006</span>f39b75964
# Removing intermediate container <span class="hljs-number">006</span>f39b75964
#  ---&gt; <span class="hljs-number">6943</span>f7ef9376
# Step <span class="hljs-number">3</span>/<span class="hljs-number">7</span> : ARG FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> ffaf89078594
# Removing intermediate container ffaf89078594
#  ---&gt; <span class="hljs-number">91</span>b5cdb6dabe
# Step <span class="hljs-number">4</span>/<span class="hljs-number">7</span> : ARG EXTENSION=<span class="hljs-string">"tar.gz"</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> d0f5188444b6
# Removing intermediate container d0f5188444b6
#  ---&gt; <span class="hljs-number">9626</span>f941ccb2
# Step <span class="hljs-number">5</span>/<span class="hljs-number">7</span> : ADD https:<span class="hljs-comment">//nginx.org/download/${FILENAME}.${EXTENSION} .</span>
# Downloading [==================================================&gt;]  <span class="hljs-number">1.049</span>MB/<span class="hljs-number">1.049</span>MB
#  ---&gt; a8e8dcca1be8
# Step <span class="hljs-number">6</span>/<span class="hljs-number">7</span> : RUN apt-get update &amp;&amp;     apt-get install build-essential                     libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp;     cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=<span class="hljs-regexp">/usr/</span>bin/nginx         --conf-path=<span class="hljs-regexp">/etc/</span>nginx/nginx.conf         --error-log-path=<span class="hljs-regexp">/var/</span>log/nginx/error.log         --http-log-path=<span class="hljs-regexp">/var/</span>log/nginx/access.log         --<span class="hljs-keyword">with</span>-pcre         --pid-path=<span class="hljs-regexp">/var/</span>run/nginx.pid         --<span class="hljs-keyword">with</span>-http_ssl_module &amp;&amp;     make &amp;&amp; make install &amp;&amp;     cd / &amp;&amp; rm -rfv /${FILENAME} &amp;&amp;     apt-get remove build-essential                     libpcre3-dev                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get autoremove -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; Running in e5675cad1260
### LONG INSTALLATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container e5675cad1260
#  ---&gt; dc7e4161f975
# Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in b579e4600247
# Removing intermediate container b579e4600247
#  ---&gt; 512aa6a95a93
# Successfully built 512aa6a95a93
# Successfully tagged custom-nginx:built

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED              SIZE
# custom-nginx       built     512aa6a95a93   About a minute ago   81.6MB
# nginx              stable    b9e1dc12387a   11 days ago          133MB</span>
</code></pre><p>As you can see, the image size has gone from being 343MB to 81.6MB. The official image is 133MB. This is a pretty optimized build, but we can go a bit further in the next sub-section.</p>
<h3 id="heading-embracing-alpine-linux">Embracing Alpine Linux</h3>
<p>If you've been fiddling around with containers for some time now, you may have heard about something called <a target="_blank" href="https://alpinelinux.org/">Alpine Linux</a>. It's a full-featured <a target="_blank" href="https://en.wikipedia.org/wiki/Linux">Linux</a> distribution like <a target="_blank" href="https://ubuntu.com/">Ubuntu</a>, <a target="_blank" href="https://www.debian.org/">Debian</a> or <a target="_blank" href="https://getfedora.org/">Fedora</a>. </p>
<p>But the good thing about Alpine is that it's built around <code>musl</code> <code>libc</code> and <code>busybox</code> and is lightweight. Where the latest <a target="_blank" href="https://hub.docker.com/_/ubuntu">ubuntu</a> image weighs at around 28MB, <a target="_blank" href="https://hub.docker.com/_/alpine">alpine</a> is 2.8MB. </p>
<p>Apart from the lightweight nature, Alpine is also secure and is a much better fit for creating containers than the other distributions.</p>
<p>Although not as user friendly as the other commercial distributions, the transition to Alpine is still very simple. In this sub-section you'll learn about recreating the <code>custom-nginx</code> image using the Alpine image as its base.</p>
<p>Open up your <code>Dockerfile</code> and update its content as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> alpine:latest

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">ARG</span> FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
<span class="hljs-keyword">ARG</span> EXTENSION=<span class="hljs-string">"tar.gz"</span>

<span class="hljs-keyword">ADD</span><span class="bash"> https://nginx.org/download/<span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apk add --no-cache pcre zlib &amp;&amp; \
    apk add --no-cache \
            --virtual .build-deps \
            build-base \ </span>
            pcre-dev \
            zlib-dev \
            openssl-dev &amp;&amp; \
    tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp; \
    cd ${FILENAME} &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/<span class="hljs-keyword">run</span><span class="bash">/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install &amp;&amp; \
    <span class="hljs-built_in">cd</span> / &amp;&amp; rm -rfv /<span class="hljs-variable">${FILENAME}</span> &amp;&amp; \
    apk del .build-deps</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>The code is almost identical except for a few changes. I'll be listing the changes and explaining them as I go:</p>
<ul>
<li>Instead of using <code>apt-get install</code> for installing packages, we use <code>apk add</code>. The <code>--no-cache</code> option means that the downloaded package won't be cached. Likewise we'll use <code>apk del</code> instead of <code>apt-get remove</code> to uninstall packages.</li>
<li>The <code>--virtual</code> option for the <code>apk add</code> command is used for bundling a bunch of packages into a single virtual package for easier management. Packages that are needed only for building the program are labeled as <code>.build-deps</code> which are then removed on line 29 by executing the <code>apk del .build-deps</code> command. You can learn more about <a target="_blank" href="https://docs.alpinelinux.org/user-handbook/0.1a/Working/apk.html#_virtuals">virtuals</a> in the official docs.</li>
<li>The package names are a bit different here. Usually every Linux distribution has its package repository available to everyone where you can search for packages. If you know the packages required for a certain task, then you can just head over to the designated repository for a distribution and search for it. You can <a target="_blank" href="https://pkgs.alpinelinux.org/packages">look up Alpine Linux packages here</a>.</li>
</ul>
<p>Now build a new image using this <code>Dockerfile</code> and see the difference in file size:</p>
<pre><code>docker image build --tag custom-nginx:built .

# Sending build context to Docker daemon  <span class="hljs-number">1.055</span>MB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM alpine:latest
#  ---&gt; <span class="hljs-number">7731472</span>c3f2a
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">8336</span>cfaaa48d
# Removing intermediate container <span class="hljs-number">8336</span>cfaaa48d
#  ---&gt; d448a9049d01
# Step <span class="hljs-number">3</span>/<span class="hljs-number">7</span> : ARG FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> bb8b2eae9d74
# Removing intermediate container bb8b2eae9d74
#  ---&gt; <span class="hljs-number">87</span>ca74f32fbe
# Step <span class="hljs-number">4</span>/<span class="hljs-number">7</span> : ARG EXTENSION=<span class="hljs-string">"tar.gz"</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> aa09627fe48c
# Removing intermediate container aa09627fe48c
#  ---&gt; <span class="hljs-number">70</span>cb557adb10
# Step <span class="hljs-number">5</span>/<span class="hljs-number">7</span> : ADD https:<span class="hljs-comment">//nginx.org/download/${FILENAME}.${EXTENSION} .</span>
# Downloading [==================================================&gt;]  <span class="hljs-number">1.049</span>MB/<span class="hljs-number">1.049</span>MB
#  ---&gt; b9790ce0c4d6
# Step <span class="hljs-number">6</span>/<span class="hljs-number">7</span> : RUN apk add --no-cache pcre zlib &amp;&amp;     apk add --no-cache             --virtual .build-deps             build-base             pcre-dev             zlib-dev             openssl-dev &amp;&amp;     tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp;     cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=<span class="hljs-regexp">/usr/</span>bin/nginx         --conf-path=<span class="hljs-regexp">/etc/</span>nginx/nginx.conf         --error-log-path=<span class="hljs-regexp">/var/</span>log/nginx/error.log         --http-log-path=<span class="hljs-regexp">/var/</span>log/nginx/access.log         --<span class="hljs-keyword">with</span>-pcre         --pid-path=<span class="hljs-regexp">/var/</span>run/nginx.pid         --<span class="hljs-keyword">with</span>-http_ssl_module &amp;&amp;     make &amp;&amp; make install &amp;&amp;     cd / &amp;&amp; rm -rfv /${FILENAME} &amp;&amp;     apk del .build-deps
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>b301f64ffc1
### LONG INSTALLATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">0</span>b301f64ffc1
#  ---&gt; dc7e4161f975
# Step <span class="hljs-number">7</span>/<span class="hljs-number">7</span> : CMD [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]
#  ---&gt; Running <span class="hljs-keyword">in</span> b579e4600247
# Removing intermediate container b579e4600247
#  ---&gt; <span class="hljs-number">3e186</span>a3c6830
# Successfully built <span class="hljs-number">3e186</span>a3c6830
# Successfully tagged custom-nginx:built

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED         SIZE
# custom-nginx       built     <span class="hljs-number">3e186</span>a3c6830   <span class="hljs-number">8</span> seconds ago   <span class="hljs-number">12.8</span>MB
</code></pre><p>Where the ubuntu version was 81.6MB, the alpine one has come down to 12.8MB which is a massive gain. Apart from the <code>apk</code> package manager, there are some other things that differ in Alpine from Ubuntu but they're not that big a deal. You can just search the internet whenever you get stuck.</p>
<h3 id="heading-how-to-create-executable-docker-images">How to Create Executable Docker Images</h3>
<p>In the previous section you worked with the <a target="_blank" href="https://hub.docker.com/r/fhsinchy/rmbyext">fhsinchy/rmbyext</a> image. In this section you'll learn how to make such an executable image. </p>
<p>To begin with, open up the directory where you've cloned the repository that came with this book. The code for the <code>rmbyext</code> application resides inside the sub-directory with the same name.</p>
<p>Before you start working on the <code>Dockerfile</code> take a moment to plan out what the final output should be. In my opinion it should be like something like this:</p>
<ul>
<li>The image should have Python pre-installed.</li>
<li>It should contain a copy of my <code>rmbyext</code> script.</li>
<li>A working directory should be set where the script will be executed.</li>
<li>The <code>rmbyext</code> script should be set as the entry-point so the image can take extension names as arguments.</li>
</ul>
<p>To build the above mentioned image, take the following steps:</p>
<ul>
<li>Get a good base image for running Python scripts, like <a target="_blank" href="https://hub.docker.com/_/python">python</a>.</li>
<li>Set-up the working directory to an easily accessible directory.</li>
<li>Install Git so that the script can be installed from my GitHub repository.</li>
<li>Install the script using Git and pip.</li>
<li>Get rid of the build's unnecessary packages.</li>
<li>Set <code>rmbyext</code> as the entry-point for this image.</li>
</ul>
<p>Now create a new <code>Dockerfile</code> inside the <code>rmbyext</code> directory and put the following code in it:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3</span>-alpine

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /zone</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apk add --no-cache git &amp;&amp; \
    pip install git+https://github.com/fhsinchy/rmbyext.git<span class="hljs-comment">#egg=rmbyext &amp;&amp; \</span>
    apk del git</span>

<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [ <span class="hljs-string">"rmbyext"</span> ]</span>
</code></pre>
<p>The explanation for the instructions in this file is as follows:</p>
<ul>
<li>The <code>FROM</code> instruction sets <a target="_blank" href="https://hub.docker.com/_/python">python</a> as the base image, making an ideal environment for running Python scripts. The <code>3-alpine</code> tag indicates that you want the Alpine variant of Python 3.</li>
<li>The <code>WORKDIR</code> instruction sets the default working directory to <code>/zone</code> here. The name of the working directory is completely random here. I found zone to be a fitting name, you may use anything you want.</li>
<li>Given the <code>rmbyext</code> script is installed from GitHub, <code>git</code> is an install time dependency. The <code>RUN</code> instruction on line 5 installs <code>git</code> then installs the <code>rmbyext</code> script using Git and pip. It also gets rid of <code>git</code> afterwards.</li>
<li>Finally on line 9, the <code>ENTRYPOINT</code> instruction sets the <code>rmbyext</code> script as the entry-point for this image.</li>
</ul>
<p>In this entire file, line 9 is the magic that turns this seemingly normal image into an executable one. Now to build the image you can execute following command:</p>
<pre><code>docker image build --tag rmbyext .

# Sending build context to Docker daemon  <span class="hljs-number">2.048</span>kB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">4</span> : FROM python:<span class="hljs-number">3</span>-alpine
# <span class="hljs-number">3</span>-alpine: Pulling <span class="hljs-keyword">from</span> library/python
# <span class="hljs-number">801</span>bfaa63ef2: Already exists 
# <span class="hljs-number">8723</span>b2b92bec: Already exists 
# <span class="hljs-number">4e07029</span>ccd64: Already exists 
# <span class="hljs-number">594990504179</span>: Already exists 
# <span class="hljs-number">140</span>d7fec7322: Already exists 
# Digest: sha256:<span class="hljs-number">7492</span>c1f615e3651629bd6c61777e9660caa3819cf3561a47d1d526dfeee02cf6
# Status: Downloaded newer image <span class="hljs-keyword">for</span> python:<span class="hljs-number">3</span>-alpine
#  ---&gt; d4d4f50f871a
# Step <span class="hljs-number">2</span>/<span class="hljs-number">4</span> : WORKDIR /zone
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">454374612</span>a91
# Removing intermediate container <span class="hljs-number">454374612</span>a91
#  ---&gt; <span class="hljs-number">7</span>f7e49bc98d2
# Step <span class="hljs-number">3</span>/<span class="hljs-number">4</span> : RUN apk add --no-cache git &amp;&amp;     pip install git+https:<span class="hljs-comment">//github.com/fhsinchy/rmbyext.git#egg=rmbyext &amp;&amp;     apk del git</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">27e2</span>e96dc95a
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">27e2</span>e96dc95a
#  ---&gt; <span class="hljs-number">3</span>c7389432e36
# Step <span class="hljs-number">4</span>/<span class="hljs-number">4</span> : ENTRYPOINT [ <span class="hljs-string">"rmbyext"</span> ]
#  ---&gt; Running <span class="hljs-keyword">in</span> f239bbea1ca6
# Removing intermediate container f239bbea1ca6
#  ---&gt; <span class="hljs-number">1746</span>b0cedbc7
# Successfully built <span class="hljs-number">1746</span>b0cedbc7
# Successfully tagged rmbyext:latest

docker image ls

# REPOSITORY         TAG        IMAGE ID       CREATED         SIZE
# rmbyext            latest     <span class="hljs-number">1746</span>b0cedbc7   <span class="hljs-number">4</span> minutes ago   <span class="hljs-number">50.9</span>MB
</code></pre><p>Here I haven't provided any tag after the image name, so the image has been tagged as <code>latest</code> by default. You should be able to run the image as you saw in the previous section. Remember to refer to the actual image name you've set, instead of <code>fhsinchy/rmbyext</code> here.</p>
<h3 id="heading-how-to-share-your-docker-images-online">How to Share Your Docker Images Online</h3>
<p>Now that you know how to make images, it's time to share them with the world. Sharing images online is easy. All you need is an account at any of the online registries. I'll be using <a target="_blank" href="https://hub.docker.com/">Docker Hub</a> here. </p>
<p>Navigate to the <a target="_blank" href="https://hub.docker.com/signup">Sign Up</a> page and create a free account. A free account allows you to host unlimited public repositories and one private repository.</p>
<p>Once you've created the account, you'll have to sign in to it using the docker CLI. So open up your terminal and execute the following command to do so:</p>
<pre><code>docker login

# Login <span class="hljs-keyword">with</span> your Docker ID to push and pull images <span class="hljs-keyword">from</span> Docker Hub. If you don<span class="hljs-string">'t have a Docker ID, head over to https://hub.docker.com to create one.
# Username: fhsinchy
# Password: 
# WARNING! Your password will be stored unencrypted in /home/fhsinchy/.docker/config.json.
# Configure a credential helper to remove this warning. See
# https://docs.docker.com/engine/reference/commandline/login/#credentials-store
#
# Login Succeeded</span>
</code></pre><p>You'll be prompted for your username and password. If you input them properly, you should be logged in to your account successfully.</p>
<p>In order to share an image online, the image has to be tagged. You've already learned about tagging in a previous sub-section. Just to refresh your memory, the generic syntax for the <code>--tag</code> or <code>-t</code> option is as follows:</p>
<pre><code>--tag &lt;image repository&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">image</span> <span class="hljs-attr">tag</span>&gt;</span></span>
</code></pre><p>As an example, let's share the <code>custom-nginx</code> image online. To do so, open up a new terminal window inside the <code>custom-nginx</code> project directory. </p>
<p>To share an image online, you'll have to tag it following the <code>&lt;docker hub username&gt;/&lt;image name&gt;:&lt;image tag&gt;</code> syntax. My username is <code>fhsinchy</code> so the command will look like this:</p>
<pre><code>docker image build --tag fhsinchy/custom-nginx:latest --file Dockerfile.built .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">9</span> : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step <span class="hljs-number">2</span>/<span class="hljs-number">9</span> : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; cbe1ced3da11
### LONG INSTALLATION STUFF GOES HERE ###
# Step 3/9 : ARG FILENAME="nginx-1.19.2"
#  ---&gt; Running in 33b62a0e9ffb
# Removing intermediate container 33b62a0e9ffb
#  ---&gt; fafc0aceb9c8
# Step 4/9 : ARG EXTENSION="tar.gz"
#  ---&gt; Running in 5c32eeb1bb11
# Removing intermediate container 5c32eeb1bb11
#  ---&gt; 36efdf6efacc
# Step 5/9 : ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .
# Downloading [==================================================&gt;]  1.049MB/1.049MB
#  ---&gt; dba252f8d609
# Step 6/9 : RUN tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION}
#  ---&gt; Running in 2f5b091b2125
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 2f5b091b2125
#  ---&gt; 2c9a325d74f1
# Step 7/9 : RUN cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in 11cc82dd5186
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container 11cc82dd5186
#  ---&gt; 6c122e485ec8
# Step 8/9 : RUN rm -rf /${FILENAME}}
#  ---&gt; Running in 04102366960b
# Removing intermediate container 04102366960b
#  ---&gt; 6bfa35420a73
# Step 9/9 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 63ee44b571bb
# Removing intermediate container 63ee44b571bb
#  ---&gt; 4ce79556db1b
# Successfully built 4ce79556db1b
# Successfully tagged fhsinchy/custom-nginx:latest</span>
</code></pre><p>In this command the <code>fhsinchy/custom-nginx</code> is the image repository and <code>latest</code> is the tag. The image name can be anything you want and can not be changed once you've uploaded the image. The tag can be changed whenever you want and usually reflects the version of the software or different kind of builds.</p>
<p>Take the <code>node</code> image as an example. The <code>node:lts</code> image refers to the long term support version of Node.js whereas the <code>node:lts-alpine</code> version refers to the Node.js version built for Alpine Linux, which is much smaller than the regular one.</p>
<p>If you do not give the image any tag, it'll be automatically tagged as <code>latest</code>. But that doesn't mean that the <code>latest</code> tag will always refer to the latest version. If, for some reason, you explicitly tag an older version of the image as <code>latest</code>, then Docker will not make any extra effort to cross check that.</p>
<p>Once the image has been built, you can them upload it by executing the following command:</p>
<pre><code>docker image push &lt;image repository&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">image</span> <span class="hljs-attr">tag</span>&gt;</span></span>
</code></pre><p>So in my case the command will be as follows:</p>
<pre><code>docker image push fhsinchy/custom-nginx:latest

# The push refers to repository [docker.io/fhsinchy/custom-nginx]
# <span class="hljs-number">4352</span>b1b1d9f5: Pushed 
# a4518dd720bd: Pushed 
# <span class="hljs-number">1</span>d756dc4e694: Pushed 
# d7a7e2b6321a: Pushed 
# f6253634dc78: Mounted <span class="hljs-keyword">from</span> library/ubuntu 
# <span class="hljs-number">9069</span>f84dbbe9: Mounted <span class="hljs-keyword">from</span> library/ubuntu 
# bacd3af13903: Mounted <span class="hljs-keyword">from</span> library/ubuntu 
# latest: digest: sha256:ffe93440256c9edb2ed67bf3bba3c204fec3a46a36ac53358899ce1a9eee497a size: <span class="hljs-number">1788</span>
</code></pre><p>Depending on the image size, the upload may take some time. Once it's done you should able to find the image in your hub profile page.</p>
<h2 id="heading-how-to-containerize-a-javascript-application">How to Containerize a JavaScript Application</h2>
<p>Now that you've got some idea of how to create images, it's time to work with something a bit more relevant. </p>
<p>In this sub-section, you'll be working with the source code of the <a target="_blank" href="https://hub.docker.com/r/fhsinchy/hello-dock">fhsinchy/hello-dock</a> image that you worked with on a previous section. In the process of containerizing this very simple application, you'll be introduced to volumes and multi-staged builds, two of the most important concepts in Docker.</p>
<h3 id="heading-how-to-write-the-development-dockerfile">How to Write the Development Dockerfile</h3>
<p>To begin with, open up the directory where you've cloned the repository that came with this book. Code for the <code>hello-dock</code> application resides inside the sub-directory with the same name.</p>
<p>This is a very simple JavaScript project powered by the <a target="_blank" href="https://github.com/vitejs/vite">vitejs/vite</a> project. Don't worry though, you don't need to know JavaScript or vite in order to go through this sub-section. Having a basic understanding of <a target="_blank" href="https://nodejs.org/">Node.js</a> and <a target="_blank" href="https://www.npmjs.com/">npm</a> will suffice.</p>
<p>Just like any other project you've done in the previous sub-section, you'll begin by making a plan of how you want this application to run. In my opinion, the plan should be as follows:</p>
<ul>
<li>Get a good base image for running JavaScript applications, like <a target="_blank" href="https://hub.docker.com/_/node">node</a>.</li>
<li>Set the default working directory inside the image.</li>
<li>Copy the <code>package.json</code> file into the image.</li>
<li>Install necessary dependencies.</li>
<li>Copy the rest of the project files.</li>
<li>Start the <code>vite</code> development server by executing <code>npm run dev</code> command.</li>
</ul>
<p>This plan should always come from the developer of the application that you're containerizing. If you're the developer yourself, then you should already have a proper understanding of how this application needs to be run. </p>
<p>Now if you put the above mentioned plan inside <code>Dockerfile.dev</code>, the file should look like as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:lts-alpine

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>

<span class="hljs-keyword">USER</span> node

<span class="hljs-keyword">RUN</span><span class="bash"> mkdir -p /home/node/app</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /home/node/app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> ./package.json .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [ <span class="hljs-string">"npm"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"dev"</span> ]</span>
</code></pre>
<p>The explanation for this code is as follows:</p>
<ul>
<li>The <code>FROM</code> instruction here sets the official Node.js image as the base, giving you all the goodness of Node.js necessary to run any JavaScript application. The <code>lts-alpine</code> tag indicates that you want to use the Alpine variant, long term support version of the image. Available tags and necessary documentation for the image can be found on the <a target="_blank" href="https://hub.docker.com/_/node">node</a> hub page.</li>
<li>The <code>USER</code> instruction sets the default user for the image to <code>node</code>. By default Docker runs containers as the root user. But according to <a target="_blank" href="https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md">Docker and Node.js Best Practices</a> this can pose a security threat. So it's a better idea to run as a non-root user whenever possible. The node image comes with a non-root user named <code>node</code> which you can set as the default user using the <code>USER</code> instruction.</li>
<li>The <code>RUN mkdir -p /home/node/app</code> instruction creates a directory called <code>app</code> inside the home directory of the <code>node</code> user. The home directory for any non-root user in Linux is usually <code>/home/&lt;user name&gt;</code> by default.</li>
<li>Then the <code>WORKDIR</code> instruction sets the default working directory to the newly created <code>/home/node/app</code> directory. By default the working directory of any image is the root. You don't want any unnecessary files sprayed all over your root directory, do you? Hence you change the default working directory to something more sensible like <code>/home/node/app</code> or whatever you like. This working directory will be applicable to any subsequent <code>COPY</code>, <code>ADD</code>, <code>RUN</code> and <code>CMD</code> instructions.</li>
<li>The <code>COPY</code> instruction here copies the <code>package.json</code> file which contains information regarding all the necessary dependencies for this application. The <code>RUN</code> instruction executes the <code>npm install</code> command which is the default command for installing dependencies using a <code>package.json</code> file in Node.js projects. The <code>.</code> at the end represents the working directory.</li>
<li>The second <code>COPY</code> instruction copies the rest of the content from the current directory (<code>.</code>) of the host filesystem to the working directory (<code>.</code>) inside the image.</li>
<li>Finally, the <code>CMD</code> instruction here sets the default command for this image which is <code>npm run dev</code> written in <code>exec</code> form.</li>
<li>The <code>vite</code> development server by default runs on port <code>3000</code> , and adding an <code>EXPOSE</code> command seemed like a good idea, so there you go.</li>
</ul>
<p>Now, to build an image from this <code>Dockerfile.dev</code> you can execute the following command:</p>
<pre><code>docker image build --file Dockerfile.dev --tag hello-dock:dev .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM node:lts
#  ---&gt; b90fa0d7cbd1
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : EXPOSE <span class="hljs-number">3000</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">722</span>d639badc7
# Removing intermediate container <span class="hljs-number">722</span>d639badc7
#  ---&gt; e2a8aa88790e
# Step <span class="hljs-number">3</span>/<span class="hljs-number">7</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">998e254</span>b4d22
# Removing intermediate container <span class="hljs-number">998e254</span>b4d22
#  ---&gt; <span class="hljs-number">6</span>bd4c42892a4
# Step <span class="hljs-number">4</span>/<span class="hljs-number">7</span> : COPY ./package.json .
#  ---&gt; <span class="hljs-number">24</span>fc5164a1dc
# Step <span class="hljs-number">5</span>/<span class="hljs-number">7</span> : RUN npm install
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">23</span>b4de3f930b
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">23</span>b4de3f930b
#  ---&gt; c17ecb19a210
# Step <span class="hljs-number">6</span>/<span class="hljs-number">7</span> : COPY . .
#  ---&gt; afb6d9a1bc76
# Step <span class="hljs-number">7</span>/<span class="hljs-number">7</span> : CMD [ <span class="hljs-string">"npm"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"dev"</span> ]
#  ---&gt; Running <span class="hljs-keyword">in</span> a7ff529c28fe
# Removing intermediate container a7ff529c28fe
#  ---&gt; <span class="hljs-number">1792250</span>adb79
# Successfully built <span class="hljs-number">1792250</span>adb79
# Successfully tagged hello-dock:dev
</code></pre><p>Given the filename is not <code>Dockerfile</code> you have to explicitly pass the filename using the <code>--file</code> option. A container can be run using this image by executing the following command:</p>
<pre><code>docker container run \
    --rm \
    --detach \
    --publish <span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> \
    --name hello-dock-dev \
    hello-dock:dev

# <span class="hljs-number">21</span>b9b1499d195d85e81f0e8bce08f43a64b63d589c5f15cbbd0b9c0cb07ae268
</code></pre><p>Now visit <code>http://127.0.0.1:3000</code> to see the <code>hello-dock</code> application in action.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock-dev.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Congratulations on running your first real-world application inside a container. The code you've just written is okay but there is one big issue with it and a few places where it can be improved. Let's begin with the issue first.</p>
<h3 id="heading-how-to-work-with-bind-mounts-in-docker">How to Work With Bind Mounts in Docker</h3>
<p>If you've worked with any front-end JavaScript framework before, you should know that the development servers in these frameworks usually come with a hot reload feature. That is if you make a change in your code, the server will reload, automatically reflecting any changes you've made immediately.</p>
<p>But if you make any changes in your code right now, you'll see nothing happening to your application running in the browser. This is because you're making changes in the code that you have in your local file system but the application you're seeing in the browser resides inside the container file system.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/local-vs-container-file-system.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To solve this issue, you can again make use of a <a target="_blank" href="https://docs.docker.com/storage/bind-mounts/">bind mount</a>. Using bind mounts, you can easily mount one of your local file system directories inside a container. Instead of making a copy of the local file system, the bind mount can reference the local file system directly from inside the container.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/bind-mounts.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This way, any changes you make to your local source code will reflect immediately inside the container,  triggering the hot reload feature of the <code>vite</code> development server. Changes made to the file system inside the container will be reflected on your local file system as well.</p>
<p>You've already learned in the <a class="post-section-overview" href="#working-with-executable-images">Working With Executable Images</a> sub-section, bind mounts can be created using the <code>--volume</code> or <code>-v</code> option for the <code>container run</code> or <code>container start</code> commands. Just to remind you, the generic syntax is as follows:</p>
<pre><code>--volume &lt;local file system directory absolute path&gt;:&lt;container file system directory absolute path&gt;:&lt;read write access&gt;
</code></pre><p>Stop your previously started <code>hello-dock-dev</code> container, and start a new container by executing the following command:</p>
<pre><code>docker container run \
    --rm \
    --publish <span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> \
    --name hello-dock-dev \
    --volume $(pwd):<span class="hljs-regexp">/home/</span>node/app \
    hello-dock:dev

# sh: <span class="hljs-number">1</span>: vite: not found
# npm ERR! code ELIFECYCLE
# npm ERR! syscall spawn
# npm ERR! file sh
# npm ERR! errno ENOENT
# npm ERR! hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> dev: <span class="hljs-string">`vite`</span>
# npm ERR! spawn ENOENT
# npm ERR!
# npm ERR! Failed at the hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> dev script.
# npm ERR! This is probably not a problem <span class="hljs-keyword">with</span> npm. There is likely additional logging output above.
# npm WARN Local package.json exists, but node_modules missing, did you mean to install?
</code></pre><p>Keep in mind, I've omitted the <code>--detach</code> option and that's to demonstrate a very important point. As you can see, the application is not running at all now.</p>
<p>That's because although the usage of a volume solves the issue of hot reloads, it introduces another problem. If you have any previous experience with Node.js, you may know that the dependencies of a Node.js project live inside the <code>node_modules</code> directory on the project root.</p>
<p>Now that you're mounting the project root on your local file system as a volume inside the container, the content inside the container gets replaced along with the <code>node_modules</code> directory containing all the dependencies. This means that the <code>vite</code> package has gone missing.</p>
<h3 id="heading-how-to-work-with-anonymous-volumes-in-docker">How to Work With Anonymous Volumes in Docker</h3>
<p>This problem can be solved using an anonymous volume. An anonymous volume is identical to a bind mount except that you don't need to specify the source directory here. The generic syntax for creating an anonymous volume is as follows:</p>
<pre><code>--volume &lt;container file system directory absolute path&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">read</span> <span class="hljs-attr">write</span> <span class="hljs-attr">access</span>&gt;</span></span>
</code></pre><p>So the final command for starting the <code>hello-dock</code> container with both volumes should be as follows:</p>
<pre><code>docker container run \
    --rm \
    --detach \
    --publish <span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> \
    --name hello-dock-dev \
    --volume $(pwd):<span class="hljs-regexp">/home/</span>node/app \
    --volume /home/node/app/node_modules \
    hello-dock:dev

# <span class="hljs-number">53</span>d1cfdb3ef148eb6370e338749836160f75f076d0fbec3c2a9b059a8992de8b
</code></pre><p>Here, Docker will take the entire <code>node_modules</code> directory from inside the container and tuck it away in some other directory managed by the Docker daemon on your host file system and will mount that directory as <code>node_modules</code> inside the container.</p>
<h3 id="heading-how-to-perform-multi-staged-builds-in-docker">How to Perform Multi-Staged Builds in Docker</h3>
<p>So far in this section, you've built an image for running a JavaScript application in development mode. Now if you want to build the image in production mode, some new challenges show up. </p>
<p>In development mode the <code>npm run serve</code> command starts a development server that serves the application to the user. That server not only serves the files but also provides the hot reload feature.</p>
<p>In production mode, the <code>npm run build</code> command compiles all your JavaScript code into some static HTML, CSS, and JavaScript files. To run these files you don't need node or any other runtime dependencies. All you need is a server like <code>nginx</code> for example.</p>
<p>To create an image where the application runs in production mode, you can take the following steps:</p>
<ul>
<li>Use <code>node</code> as the base image and build the application.</li>
<li>Install <code>nginx</code> inside the node image and use that to serve the static files.</li>
</ul>
<p>This approach is completely valid. But the problem is that the <code>node</code> image is big and most of the stuff it carries is unnecessary to serve your static files. A better approach to this scenario is as follows:</p>
<ul>
<li>Use <code>node</code> image as the base and build the application.</li>
<li>Copy the files created using the <code>node</code> image to an <code>nginx</code> image.</li>
<li>Create the final image based on <code>nginx</code> and discard all <code>node</code> related stuff.</li>
</ul>
<p>This way your image only contains the files that are needed and becomes really handy. </p>
<p>This approach is a multi-staged build. To perform such a build, create a new <code>Dockerfile</code> inside your <code>hello-dock</code> project directory and put the following content in it:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:lts-alpine as builder

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> ./package.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build</span>

<span class="hljs-keyword">FROM</span> nginx:stable-alpine

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/dist /usr/share/nginx/html</span>
</code></pre>
<p>As you can see the <code>Dockerfile</code> looks a lot like your previous ones with a few oddities. The explanation for this file is as follows:</p>
<ul>
<li>Line 1 starts the first stage of the build using <code>node:lts-alpine</code> as the base image. The <code>as builder</code> syntax assigns a name to this stage so that it can be referred to later on.</li>
<li>From line 3 to line 9, it's standard stuff that you've seen many times before. The <code>RUN npm run build</code> command actually compiles the entire application and tucks it inside <code>/app/dist</code> directory where <code>/app</code> is the working directory and <code>/dist</code> is the default output directory for <code>vite</code> applications.</li>
<li>Line 11 starts the second stage of the build using <code>nginx:stable-alpine</code> as the base image.</li>
<li>The NGINX server runs on port 80 by default so the line <code>EXPOSE 80</code> is added.</li>
<li>The last line is a <code>COPY</code> instruction. The <code>--from=builder</code> part indicates that you want to copy some files from the <code>builder</code> stage. After that it's a standard copy instruction where <code>/app/dist</code> is the source and <code>/usr/share/nginx/html</code> is the destination. The destination used here is the default site path for NGINX so any static file you put inside there will be automatically served.</li>
</ul>
<p>As you can see, the resulting image is a <code>nginx</code> base image containing only the files necessary for running the application. To build this image execute the following command:</p>
<pre><code>docker image build --tag hello-dock:prod .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">9</span> : FROM node:lts-alpine <span class="hljs-keyword">as</span> builder
#  ---&gt; <span class="hljs-number">72</span>aaced1868f
# Step <span class="hljs-number">2</span>/<span class="hljs-number">9</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> e361c5c866dd
# Removing intermediate container e361c5c866dd
#  ---&gt; <span class="hljs-number">241</span>b4b97b34c
# Step <span class="hljs-number">3</span>/<span class="hljs-number">9</span> : COPY ./package.json ./
#  ---&gt; <span class="hljs-number">6</span>c594c5d2300
# Step <span class="hljs-number">4</span>/<span class="hljs-number">9</span> : RUN npm install
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">6</span>dfabf0ee9f8
# npm WARN deprecated fsevents@<span class="hljs-number">2.1</span><span class="hljs-number">.3</span>: Please update to v <span class="hljs-number">2.2</span>.x
#
# &gt; esbuild@<span class="hljs-number">0.8</span><span class="hljs-number">.29</span> postinstall /app/node_modules/esbuild
# &gt; node install.js
#
# npm notice created a lockfile <span class="hljs-keyword">as</span> package-lock.json. You should commit <span class="hljs-built_in">this</span> file.
# npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@~<span class="hljs-number">2.1</span><span class="hljs-number">.2</span> (node_modules/chokidar/node_modules/fsevents):
# npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform <span class="hljs-keyword">for</span> fsevents@<span class="hljs-number">2.1</span><span class="hljs-number">.3</span>: wanted {<span class="hljs-string">"os"</span>:<span class="hljs-string">"darwin"</span>,<span class="hljs-string">"arch"</span>:<span class="hljs-string">"any"</span>} (current: {<span class="hljs-string">"os"</span>:<span class="hljs-string">"linux"</span>,<span class="hljs-string">"arch"</span>:<span class="hljs-string">"x64"</span>})
# npm WARN hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> No description
# npm WARN hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> No repository field.
# npm WARN hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> No license field.
#
# added <span class="hljs-number">327</span> packages <span class="hljs-keyword">from</span> <span class="hljs-number">301</span> contributors and audited <span class="hljs-number">329</span> packages <span class="hljs-keyword">in</span> <span class="hljs-number">35.971</span>s
#
# <span class="hljs-number">26</span> packages are looking <span class="hljs-keyword">for</span> funding
#   run <span class="hljs-string">`npm fund`</span> <span class="hljs-keyword">for</span> details
#
# found <span class="hljs-number">0</span> vulnerabilities
#
# Removing intermediate container <span class="hljs-number">6</span>dfabf0ee9f8
#  ---&gt; <span class="hljs-number">21</span>fd1b065314
# Step <span class="hljs-number">5</span>/<span class="hljs-number">9</span> : COPY . .
#  ---&gt; <span class="hljs-number">43243</span>f95bff7
# Step <span class="hljs-number">6</span>/<span class="hljs-number">9</span> : RUN npm run build
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">4</span>d918cf18584
#
# &gt; hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> build /app
# &gt; vite build
#
# - Building production bundle...
#
# [write] dist/index.html <span class="hljs-number">0.39</span>kb, <span class="hljs-attr">brotli</span>: <span class="hljs-number">0.15</span>kb
# [write] dist/_assets/docker-handbook-github<span class="hljs-number">.3</span>adb4865.webp <span class="hljs-number">12.32</span>kb
# [write] dist/_assets/index.eabcae90.js <span class="hljs-number">42.56</span>kb, <span class="hljs-attr">brotli</span>: <span class="hljs-number">15.40</span>kb
# [write] dist/_assets/style<span class="hljs-number">.0637</span>ccc5.css <span class="hljs-number">0.16</span>kb, <span class="hljs-attr">brotli</span>: <span class="hljs-number">0.10</span>kb
# - Building production bundle...
#
# Build completed <span class="hljs-keyword">in</span> <span class="hljs-number">1.71</span>s.
#
# Removing intermediate container <span class="hljs-number">4</span>d918cf18584
#  ---&gt; <span class="hljs-number">187</span>fb3e82d0d
# Step <span class="hljs-number">7</span>/<span class="hljs-number">9</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> b3aab5cf5975
# Removing intermediate container b3aab5cf5975
#  ---&gt; d6fcc058cfda
# Step <span class="hljs-number">8</span>/<span class="hljs-number">9</span> : FROM nginx:stable-alpine
# stable: Pulling <span class="hljs-keyword">from</span> library/nginx
# <span class="hljs-number">6</span>ec7b7d162b2: Already exists 
# <span class="hljs-number">43876</span>acb2da3: Pull complete 
# <span class="hljs-number">7</span>a79edd1e27b: Pull complete 
# eea03077c87e: Pull complete 
# eba7631b45c5: Pull complete 
# Digest: sha256:<span class="hljs-number">2</span>eea9f5d6fff078ad6cc6c961ab11b8314efd91fb8480b5d054c7057a619e0c3
# Status: Downloaded newer image <span class="hljs-keyword">for</span> nginx:stable
#  ---&gt; <span class="hljs-number">05</span>f64a802c26
# Step <span class="hljs-number">9</span>/<span class="hljs-number">9</span> : COPY --<span class="hljs-keyword">from</span>=builder /app/dist /usr/share/nginx/html
#  ---&gt; <span class="hljs-number">8</span>c6dfc34a10d
# Successfully built <span class="hljs-number">8</span>c6dfc34a10d
# Successfully tagged hello-dock:prod
</code></pre><p>Once the image has been built, you may run a new container by executing the following command:</p>
<pre><code>docker container run \
    --rm \
    --detach \
    --name hello-dock-prod \
    --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> \
    hello-dock:prod

# <span class="hljs-number">224</span>aaba432bb09aca518fdd0365875895c2f5121eb668b2e7b2d5a99c019b953
</code></pre><p>The running application should be available on <code>http://127.0.0.1:8080</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here you can see my <code>hello-dock</code> application in all its glory. Multi-staged builds can be very useful if you're building large applications with a lot of dependencies. If configured properly, images built in multiple stages can be very optimized and compact.</p>
<h3 id="heading-how-to-ignore-unnecessary-files">How to Ignore Unnecessary Files</h3>
<p>If you've been working with <code>git</code> for some time now, you may know about the <code>.gitignore</code> files in projects. These contain a list of files and directories to be excluded from the repository. </p>
<p>Well, Docker has a similar concept. The <code>.dockerignore</code> file contains a list of files and directories to be excluded from image builds. You can find a pre-created <code>.dockerignore</code> file in the <code>hello-dock</code> directory.</p>
<pre><code class="lang-dockerignore">.git
*Dockerfile*
*docker-compose*
node_modules
</code></pre>
<p>This <code>.dockerignore</code> file has to be in the build context. Files and directories mentioned here will be ignored by the <code>COPY</code> instruction. But if you do a bind mount, the <code>.dockerignore</code> file will have no effect. I've added <code>.dockerignore</code> files where necessary in the project repository.</p>
<h2 id="heading-network-manipulation-basics-in-docker">Network Manipulation Basics in Docker</h2>
<p>So far in this book, you've only worked with single container projects. But in real life, the majority of projects that you'll have to work with will have more than one container. And to be honest, working with a bunch of containers can be a little difficult if you don't understand the nuances of container isolation. </p>
<p>So in this section of the book, you'll get familiar with basic networking with Docker and you'll work hands on with a small multi-container project.</p>
<p>Well you've already learned in the previous section that containers are isolated environments. Now consider a scenario where you have a <code>notes-api</code> application powered by <a target="_blank" href="https://expressjs.com/">Express.js</a> and a <a target="_blank" href="https://www.postgresql.org/">PostgreSQL</a> database server running in two separate containers.</p>
<p>These two containers are completely isolated from each other and are oblivious to each other's existence. <strong>So how do you connect the two? Won't that be a challenge?</strong>‌</p>
<p>You may think of two possible solutions to this problem. They are as follows:</p>
<ul>
<li>Accessing the database server using an exposed port.</li>
<li>Accessing the database server using its IP address and default port.</li>
</ul>
<p>The first one involves exposing a port from the <code>postgres</code> container and the <code>notes-api</code> will connect through that. Assume that the exposed port from the <code>postgres</code> container is 5432. Now if you try to connect to <code>127.0.0.1:5432</code> from inside the <code>notes-api</code> container, you'll find that the <code>notes-api</code> can't find the database server at all.</p>
<p>The reason is that when you're saying <code>127.0.0.1</code> inside the <code>notes-api</code> container, you're simply referring to the <code>localhost</code> of that container and that container only. The <code>postgres</code> server simply doesn't exist there. As a result the <code>notes-api</code> application failed to connect.</p>
<p>The second solution you may think of is finding the exact IP address of the <code>postgres</code> container using the <code>container inspect</code> command and using that with the port. Assuming the name of the <code>postgres</code> container is <code>notes-api-db-server</code> you can easily get the IP address by executing the following command:</p>
<pre><code>docker container inspect --format=<span class="hljs-string">'{{range .NetworkSettings.Networks}} {{.IPAddress}} {{end}}'</span> notes-api-db-server

#  <span class="hljs-number">172.17</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>
</code></pre><p>Now given that the default port for <code>postgres</code> is <code>5432</code>, you can very easily access the database server by connecting to <code>172.17.0.2:5432</code> from the <code>notes-api</code> container.</p>
<p>There are problems in this approach as well. Using IP addresses to refer to a container is not recommended. Also, if the container gets destroyed and recreated, the IP address may change. Keeping track of these changing IP addresses can be pretty hectic.</p>
<p>Now that I've dismissed the possible wrong answers to the original question, the correct answer is, <strong>you connect them by putting them under a user-defined bridge network.</strong></p>
<h3 id="heading-docker-network-basics">Docker Network Basics</h3>
<p>A network in Docker is another logical object like a container and image. Just like the other two, there is a plethora of commands under the <code>docker network</code> group for manipulating networks. </p>
<p>To list out the networks in your system, execute the following command:</p>
<pre><code>docker network ls

# NETWORK ID     NAME      DRIVER    SCOPE
# c2e59f2b96bd   bridge    bridge    local
# <span class="hljs-number">124</span>dccee067f   host      host      local
# <span class="hljs-number">506e3822</span>bf1f   none      <span class="hljs-literal">null</span>      local
</code></pre><p>You should see three networks in your system. Now look at the <code>DRIVER</code> column of the table here. These drivers are can be treated as the type of network. </p>
<p>By default, Docker has five networking drivers. They are as follows:</p>
<ul>
<li><code>bridge</code> - The default networking driver in Docker. This can be used when multiple containers are running in standard mode and need to communicate with each other.</li>
<li><code>host</code> - Removes the network isolation completely. Any container running under a <code>host</code> network is basically attached to the network of the host system.</li>
<li><code>none</code> - This driver disables networking for containers altogether. I haven't found any use-case for this yet.</li>
<li><code>overlay</code> - This is used for connecting multiple Docker daemons across computers and is out of the scope of this book.</li>
<li><code>macvlan</code> - Allows assignment of MAC addresses to containers, making them function like physical devices in a network.</li>
</ul>
<p>There are also third-party plugins that allow you to integrate Docker with specialized network stacks. Out of the five mentioned above, you'll only work with the <code>bridge</code> networking driver in this book.</p>
<h3 id="heading-how-to-create-a-user-defined-bridge-in-docker">How to Create a User-Defined Bridge in Docker</h3>
<p>Before you start creating your own bridge, I would like to take some time to discuss the default bridge network that comes with Docker. Let's begin by listing all the networks on your system:</p>
<pre><code>docker network ls

# NETWORK ID     NAME      DRIVER    SCOPE
# c2e59f2b96bd   bridge    bridge    local
# <span class="hljs-number">124</span>dccee067f   host      host      local
# <span class="hljs-number">506e3822</span>bf1f   none      <span class="hljs-literal">null</span>      local
</code></pre><p>As you can see, Docker comes with a default bridge network named <code>bridge</code>. Any container you run will be automatically attached to this bridge network:</p>
<pre><code>docker container run --rm --detach --name hello-dock --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> fhsinchy/hello-dock
# a37f723dad3ae793ce40f97eb6bb236761baa92d72a2c27c24fc7fda0756657d

docker network inspect --format=<span class="hljs-string">'{{range .Containers}}{{.Name}}{{end}}'</span> bridge
# hello-dock
</code></pre><p>Containers attached to the default bridge network can communicate with each others using IP addresses which I have already discouraged in the previous sub-section.</p>
<p>A user-defined bridge, however, has some extra features over the default one. According to the official <a target="_blank" href="https://docs.docker.com/network/bridge/#differences-between-user-defined-bridges-and-the-default-bridge">docs</a> on this topic, some notable extra features are as follows:</p>
<ul>
<li><strong>User-defined bridges provide automatic DNS resolution between containers:</strong> This means containers attached to the same network can communicate with each others using the container name. So if you have two containers named <code>notes-api</code> and <code>notes-db</code> the API container will be able to connect to the database container using the <code>notes-db</code> name.</li>
<li><strong>User-defined bridges provide better isolation:</strong> All containers are attached to the default bridge network by default which can cause conflicts among them. Attaching containers to a user-defined bridge can ensure better isolation.</li>
<li><strong>Containers can be attached and detached from user-defined networks on the fly:</strong> During a container’s lifetime, you can connect or disconnect it from user-defined networks on the fly. To remove a container from the default bridge network, you need to stop the container and recreate it with different network options.</li>
</ul>
<p>Now that you've learned quite a lot about a user-defined network, it's time to create one for yourself. A network can be created using the <code>network create</code> command. The generic syntax for the command is as follows:</p>
<pre><code>docker network create &lt;network name&gt;
</code></pre><p>To create a network with the name <code>skynet</code> execute the following command:</p>
<pre><code>docker network create skynet

# <span class="hljs-number">7</span>bd5f351aa892ac6ec15fed8619fc3bbb95a7dcdd58980c28304627c8f7eb070

docker network ls

# NETWORK ID     NAME     DRIVER    SCOPE
# be0cab667c4b   bridge   bridge    local
# <span class="hljs-number">124</span>dccee067f   host     host      local
# <span class="hljs-number">506e3822</span>bf1f   none     <span class="hljs-literal">null</span>      local
# <span class="hljs-number">7</span>bd5f351aa89   skynet   bridge    local
</code></pre><p>As you can see a new network has been created with the given name. No container is currently attached to this network. In the next sub-section, you'll learn about attaching containers to a network.</p>
<h3 id="heading-how-to-attach-a-container-to-a-network-in-docker">How to Attach a Container to a Network in Docker</h3>
<p>There are mostly two ways of attaching a container to a network. First, you can use the network connect command to attach a container to a network. The generic syntax for the command is as follows:</p>
<pre><code>docker network connect &lt;network identifier&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">container</span> <span class="hljs-attr">identifier</span>&gt;</span></span>
</code></pre><p>To connect the <code>hello-dock</code> container to the <code>skynet</code> network, you can execute the following command:</p>
<pre><code>docker network connect skynet hello-dock

docker network inspect --format=<span class="hljs-string">'{{range .Containers}} {{.Name}} {{end}}'</span> skynet

#  hello-dock

docker network inspect --format=<span class="hljs-string">'{{range .Containers}} {{.Name}} {{end}}'</span> bridge

#  hello-dock
</code></pre><p>As you can see from the outputs of the two <code>network inspect</code> commands, the <code>hello-dock</code> container is now attached to both the <code>skynet</code> and the default <code>bridge</code> network.</p>
<p>The second way of attaching a container to a network is by using the <code>--network</code> option for the <code>container run</code> or <code>container create</code> commands. The generic syntax for the option is as follows:</p>
<pre><code>--network &lt;network identifier&gt;
</code></pre><p>To run another <code>hello-dock</code> container attached to the same network, you can execute the following command:</p>
<pre><code>docker container run --network skynet --rm --name alpine-box -it alpine sh

# lands you into alpine linux shell

/ # ping hello-dock

# PING hello-dock (<span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>): <span class="hljs-number">56</span> data bytes
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">0</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.191</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">1</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.103</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">2</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.139</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">3</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.142</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">4</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.146</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">5</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.095</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">6</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.181</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">7</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.138</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">8</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.158</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">9</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.137</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">10</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.145</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">11</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.138</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">12</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.085</span> ms

--- hello-dock ping statistics ---
<span class="hljs-number">13</span> packets transmitted, <span class="hljs-number">13</span> packets received, <span class="hljs-number">0</span>% packet loss
round-trip min/avg/max = <span class="hljs-number">0.085</span>/<span class="hljs-number">0.138</span>/<span class="hljs-number">0.191</span> ms
</code></pre><p>As you can see, running <code>ping hello-dock</code> from inside the <code>alpine-box</code> container works because both of the containers are under the same user-defined bridge network and automatic DNS resolution is working.</p>
<p>Keep in mind, though, that in order for the automatic DNS resolution to work you must assign custom names to the containers. Using the randomly generated name will not work.</p>
<h3 id="heading-how-to-detach-containers-from-a-network-in-docker">How to Detach Containers from a Network in Docker</h3>
<p>In the previous sub-section you learned about attaching containers to a network. In this sub-section, you'll learn about how to detach them. </p>
<p>You can use the <code>network disconnect</code> command for this task. The generic syntax for the command is as follows:</p>
<pre><code>docker network disconnect &lt;network identifier&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">container</span> <span class="hljs-attr">identifier</span>&gt;</span></span>
</code></pre><p>To detach the <code>hello-dock</code> container from the <code>skynet</code> network, you can execute the following command:</p>
<pre><code>docker network disconnect skynet hello-dock
</code></pre><p>Just like the <code>network connect</code> command, the <code>network disconnect</code> command doesn't give any output.</p>
<h3 id="heading-how-to-get-rid-of-networks-in-docker">How to Get Rid of Networks in Docker</h3>
<p>Just like the other logical objects in Docker, networks can be removed using the <code>network rm</code> command. The generic syntax for the command is as follows:</p>
<pre><code>docker network rm &lt;network identifier&gt;
</code></pre><p>To remove the <code>skynet</code> network from your system, you can execute the following command:</p>
<pre><code>docker network rm skynet
</code></pre><p>You can also use the <code>network prune</code> command to remove any unused networks from your system. The command also has the <code>-f</code> or <code>--force</code> and <code>-a</code> or <code>--all</code> options.</p>
<h2 id="heading-how-to-containerize-a-multi-container-javascript-application">How to Containerize a Multi-Container JavaScript Application</h2>
<p>Now that you've learned enough about networks in Docker, in this section you'll learn to containerize a full-fledged multi-container project. The project you'll be working with is a simple <code>notes-api</code> powered by Express.js and PostgreSQL.</p>
<p>In this project there are two containers in total that you'll have to connect using a network. Apart from this, you'll also learn about concepts like environment variables and named volumes. So without further ado, let's jump right in.</p>
<h3 id="heading-how-to-run-the-database-server">How to Run the Database Server</h3>
<p>The database server in this project is a simple PostgreSQL server and uses the official <a target="_blank" href="https://hub.docker.com/_/postgres">postgres</a> image. </p>
<p>According to the official docs, in order to run a container with this image, you must provide the <code>POSTGRES_PASSWORD</code> environment variable. Apart from this one, I'll also provide a name for the default database using the <code>POSTGRES_DB</code> environment variable. PostgreSQL by default listens on port <code>5432</code>, so you need to publish that as well.</p>
<p>To run the database server you can execute the following command:</p>
<pre><code>docker container run \
    --detach \
    --name=notes-db \
    --env POSTGRES_DB=notesdb \
    --env POSTGRES_PASSWORD=secret \
    --network=notes-api-network \
    <span class="hljs-attr">postgres</span>:<span class="hljs-number">12</span>

# a7b287d34d96c8e81a63949c57b83d7c1d71b5660c87f5172f074bd1606196dc

docker container ls

# CONTAINER ID   IMAGE         COMMAND                  CREATED              STATUS              PORTS      NAMES
# a7b287d34d96   postgres:<span class="hljs-number">12</span>   <span class="hljs-string">"docker-entrypoint.s…"</span>   About a minute ago   Up About a minute   <span class="hljs-number">5432</span>/tcp   notes-db
</code></pre><p>The <code>--env</code> option for the <code>container run</code> and <code>container create</code> commands can be used for providing environment variables to a container. As you can see, the database container has been created successfully and is running now.</p>
<p>Although the container is running, there is a small problem. Databases like PostgreSQL, MongoDB, and MySQL persist their data in a directory. PostgreSQL uses the <code>/var/lib/postgresql/data</code> directory inside the container to persist data. </p>
<p>Now what if the container gets destroyed for some reason? You'll lose all your data. To solve this problem, a named volume can be used.</p>
<h3 id="heading-how-to-work-with-named-volumes-in-docker">How to Work with Named Volumes in Docker</h3>
<p>Previously you've worked with bind mounts and anonymous volumes. A named volume is very similar to an anonymous volume except that you can refer to a named volume using its name. </p>
<p>Volumes are also logical objects in Docker and can be manipulated using the command-line. The <code>volume create</code> command can be used for creating a named volume.</p>
<p>The generic syntax for the command is as follows:</p>
<pre><code>docker volume create &lt;volume name&gt;
</code></pre><p>To create a volume named <code>notes-db-data</code> you can execute the following command:</p>
<pre><code>docker volume create notes-db-data

# notes-db-data

docker volume ls

# DRIVER    VOLUME NAME
# local     notes-db-data
</code></pre><p>This volume can now be mounted to <code>/var/lib/postgresql/data</code> inside the <code>notes-db</code> container. To do so, stop and remove the <code>notes-db</code> container:</p>
<pre><code>docker container stop notes-db

# notes-db

docker container rm notes-db

# notes-db
</code></pre><p>Now run a new container and assign the volume using the <code>--volume</code> or <code>-v</code> option.</p>
<pre><code>docker container run \
    --detach \
    --volume notes-db-data:<span class="hljs-regexp">/var/</span>lib/postgresql/data \
    --name=notes-db \
    --env POSTGRES_DB=notesdb \
    --env POSTGRES_PASSWORD=secret \
    --network=notes-api-network \
    <span class="hljs-attr">postgres</span>:<span class="hljs-number">12</span>

# <span class="hljs-number">37755e86</span>d62794ed3e67c19d0cd1eba431e26ab56099b92a3456908c1d346791
</code></pre><p>Now inspect the <code>notes-db</code> container to make sure that the mounting was successful:</p>
<pre><code>docker container inspect --format=<span class="hljs-string">'{{range .Mounts}} {{ .Name }} {{end}}'</span> notes-db

#  notes-db-data
</code></pre><p>Now the data will safely be stored inside the <code>notes-db-data</code> volume and can be reused in the future. A bind mount can also be used instead of a named volume here, but I prefer a named volume in such scenarios.</p>
<h3 id="heading-how-to-access-logs-from-a-container-in-docker">How to Access Logs from a Container in Docker</h3>
<p>In order to see the logs from a container, you can use the <code>container logs</code> command. The generic syntax for the command is as follows:</p>
<pre><code>docker container logs &lt;container identifier&gt;
</code></pre><p>To access the logs from the <code>notes-db</code> container, you can execute the following command:</p>
<pre><code>docker container logs notes-db

# The files belonging to <span class="hljs-built_in">this</span> database system will be owned by user <span class="hljs-string">"postgres"</span>.
# This user must also own the server process.

# The database cluster will be initialized <span class="hljs-keyword">with</span> locale <span class="hljs-string">"en_US.utf8"</span>.
# The <span class="hljs-keyword">default</span> database encoding has accordingly been set to <span class="hljs-string">"UTF8"</span>.
# The <span class="hljs-keyword">default</span> text search configuration will be set to <span class="hljs-string">"english"</span>.
#
# Data page checksums are disabled.
#
# fixing permissions on existing directory /<span class="hljs-keyword">var</span>/lib/postgresql/data ... ok
# creating subdirectories ... ok
# selecting dynamic shared memory implementation ... posix
# selecting <span class="hljs-keyword">default</span> max_connections ... <span class="hljs-number">100</span>
# selecting <span class="hljs-keyword">default</span> shared_buffers ... <span class="hljs-number">128</span>MB
# selecting <span class="hljs-keyword">default</span> time zone ... Etc/UTC
# creating configuration files ... ok
# running bootstrap script ... ok
# performing post-bootstrap initialization ... ok
# syncing data to disk ... ok
#
#
# Success. You can now start the database server using:
#
#     pg_ctl -D /<span class="hljs-keyword">var</span>/lib/postgresql/data -l logfile start
#
# initdb: warning: enabling <span class="hljs-string">"trust"</span> authentication <span class="hljs-keyword">for</span> local connections
# You can change <span class="hljs-built_in">this</span> by editing pg_hba.conf or using the option -A, or
# --auth-local and --auth-host, the next time you run initdb.
# waiting <span class="hljs-keyword">for</span> server to start...<span class="hljs-number">.2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21.613</span> UTC [<span class="hljs-number">47</span>] LOG:  starting PostgreSQL <span class="hljs-number">12.5</span> (Debian <span class="hljs-number">12.5</span><span class="hljs-number">-1.</span>pgdg100+<span class="hljs-number">1</span>) on x86_64-pc-linux-gnu, compiled by gcc (Debian <span class="hljs-number">8.3</span><span class="hljs-number">.0</span><span class="hljs-number">-6</span>) <span class="hljs-number">8.3</span><span class="hljs-number">.0</span>, <span class="hljs-number">64</span>-bit
# <span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21.621</span> UTC [<span class="hljs-number">47</span>] LOG:  listening on Unix socket <span class="hljs-string">"/var/run/postgresql/.s.PGSQL.5432"</span>
# <span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21.675</span> UTC [<span class="hljs-number">48</span>] LOG:  database system was shut down at <span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21</span> UTC
# <span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21.685</span> UTC [<span class="hljs-number">47</span>] LOG:  database system is ready to accept connections
#  done
# server started
# CREATE DATABASE
#
#
# /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d<span class="hljs-comment">/*
#
# 2021-01-25 13:39:22.008 UTC [47] LOG:  received fast shutdown request
# waiting for server to shut down....2021-01-25 13:39:22.015 UTC [47] LOG:  aborting any active transactions
# 2021-01-25 13:39:22.017 UTC [47] LOG:  background worker "logical replication launcher" (PID 54) exited with exit code 1
# 2021-01-25 13:39:22.017 UTC [49] LOG:  shutting down
# 2021-01-25 13:39:22.056 UTC [47] LOG:  database system is shut down
#  done
# server stopped
#
# PostgreSQL init process complete; ready for start up.
#
# 2021-01-25 13:39:22.135 UTC [1] LOG:  starting PostgreSQL 12.5 (Debian 12.5-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
# 2021-01-25 13:39:22.136 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
# 2021-01-25 13:39:22.136 UTC [1] LOG:  listening on IPv6 address "::", port 5432
# 2021-01-25 13:39:22.147 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
# 2021-01-25 13:39:22.177 UTC [75] LOG:  database system was shut down at 2021-01-25 13:39:22 UTC
# 2021-01-25 13:39:22.190 UTC [1] LOG:  database system is ready to accept connections</span>
</code></pre><p>Evident by the text in line 57, the database is up and ready to accept connections from the outside. There is also the <code>--follow</code> or <code>-f</code> option for the command which lets you attach the console to the logs output and get a continuous stream of text.</p>
<h3 id="heading-how-to-create-a-network-and-attaching-the-database-server-in-docker">How to Create a Network and Attaching the Database Server in Docker</h3>
<p>As you've learned in the previous section, the containers have to be attached to a user-defined bridge network in order to communicate with each other using container names. To do so, create a network named <code>notes-api-network</code> in your system:</p>
<pre><code>docker network create notes-api-network
</code></pre><p>Now attach the <code>notes-db</code> container to this network by executing the following command:</p>
<pre><code>docker network connect notes-api-network notes-db
</code></pre><h3 id="heading-how-to-write-the-dockerfile">How to Write the Dockerfile</h3>
<p>Go to the directory where you've cloned the project code. Inside there, go inside the <code>notes-api/api</code> directory, and create a new <code>Dockerfile</code>. Put the following code in the file:</p>
<pre><code># stage one
FROM node:lts-alpine <span class="hljs-keyword">as</span> builder

# install dependencies <span class="hljs-keyword">for</span> node-gyp
RUN apk add --no-cache python make g++

WORKDIR /app

COPY ./package.json .
RUN npm install --only=prod

# stage two
FROM node:lts-alpine

EXPOSE <span class="hljs-number">3000</span>
ENV NODE_ENV=production

USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app

COPY . .
COPY --<span class="hljs-keyword">from</span>=builder /app/node_modules  /home/node/app/node_modules

CMD [ <span class="hljs-string">"node"</span>, <span class="hljs-string">"bin/www"</span> ]
</code></pre><p>This is a multi-staged build. The first stage is used for building and installing the dependencies using <code>node-gyp</code> and the second stage is for running the application. I'll go through the steps briefly:</p>
<ul>
<li>Stage 1 uses <code>node:lts-alpine</code> as its base and uses <code>builder</code> as the stage name.</li>
<li>On line 5, we install <code>python</code>, <code>make</code>, and <code>g++</code>. The <code>node-gyp</code> tool requires these three packages to run.</li>
<li>On line 7, we set <code>/app</code> directory as the <code>WORKDIR</code> .</li>
<li>On line 9 and 10, we copy the <code>package.json</code> file to the <code>WORKDIR</code> and install all the dependencies.</li>
<li>Stage 2 also uses <code>node-lts:alpine</code> as the base.</li>
<li>On line 16, we set the <code>NODE_ENV</code> environment variable to <code>production</code>. This is important for the API to run properly.</li>
<li>From line 18 to line 20, we set the default user to <code>node</code>, create the <code>/home/node/app</code> directory, and set that as the <code>WORKDIR</code>.</li>
<li>On line 22, we copy all the project files and on line 23 we copy the <code>node_modules</code> directory from the <code>builder</code> stage. This directory contains all the built dependencies necessary for running the application.</li>
<li>On line 25, we set the default command.</li>
</ul>
<p>To build an image from this <code>Dockerfile</code>, you can execute the following command:</p>
<pre><code>docker image build --tag notes-api .

# Sending build context to Docker daemon  <span class="hljs-number">37.38</span>kB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">14</span> : FROM node:lts-alpine <span class="hljs-keyword">as</span> builder
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">2</span>/<span class="hljs-number">14</span> : RUN apk add --no-cache python make g++
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">5</span>f20a0ecc04b
# fetch http:<span class="hljs-comment">//dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz</span>
# fetch http:<span class="hljs-comment">//dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz</span>
# (<span class="hljs-number">1</span>/<span class="hljs-number">21</span>) Installing binutils (<span class="hljs-number">2.33</span><span class="hljs-number">.1</span>-r0)
# (<span class="hljs-number">2</span>/<span class="hljs-number">21</span>) Installing gmp (<span class="hljs-number">6.1</span><span class="hljs-number">.2</span>-r1)
# (<span class="hljs-number">3</span>/<span class="hljs-number">21</span>) Installing isl (<span class="hljs-number">0.18</span>-r0)
# (<span class="hljs-number">4</span>/<span class="hljs-number">21</span>) Installing libgomp (<span class="hljs-number">9.3</span><span class="hljs-number">.0</span>-r0)
# (<span class="hljs-number">5</span>/<span class="hljs-number">21</span>) Installing libatomic (<span class="hljs-number">9.3</span><span class="hljs-number">.0</span>-r0)
# (<span class="hljs-number">6</span>/<span class="hljs-number">21</span>) Installing mpfr4 (<span class="hljs-number">4.0</span><span class="hljs-number">.2</span>-r1)
# (<span class="hljs-number">7</span>/<span class="hljs-number">21</span>) Installing mpc1 (<span class="hljs-number">1.1</span><span class="hljs-number">.0</span>-r1)
# (<span class="hljs-number">8</span>/<span class="hljs-number">21</span>) Installing gcc (<span class="hljs-number">9.3</span><span class="hljs-number">.0</span>-r0)
# (<span class="hljs-number">9</span>/<span class="hljs-number">21</span>) Installing musl-dev (<span class="hljs-number">1.1</span><span class="hljs-number">.24</span>-r3)
# (<span class="hljs-number">10</span>/<span class="hljs-number">21</span>) Installing libc-dev (<span class="hljs-number">0.7</span><span class="hljs-number">.2</span>-r0)
# (<span class="hljs-number">11</span>/<span class="hljs-number">21</span>) Installing g++ (<span class="hljs-number">9.3</span><span class="hljs-number">.0</span>-r0)
# (<span class="hljs-number">12</span>/<span class="hljs-number">21</span>) Installing make (<span class="hljs-number">4.2</span><span class="hljs-number">.1</span>-r2)
# (<span class="hljs-number">13</span>/<span class="hljs-number">21</span>) Installing libbz2 (<span class="hljs-number">1.0</span><span class="hljs-number">.8</span>-r1)
# (<span class="hljs-number">14</span>/<span class="hljs-number">21</span>) Installing expat (<span class="hljs-number">2.2</span><span class="hljs-number">.9</span>-r1)
# (<span class="hljs-number">15</span>/<span class="hljs-number">21</span>) Installing libffi (<span class="hljs-number">3.2</span><span class="hljs-number">.1</span>-r6)
# (<span class="hljs-number">16</span>/<span class="hljs-number">21</span>) Installing gdbm (<span class="hljs-number">1.13</span>-r1)
# (<span class="hljs-number">17</span>/<span class="hljs-number">21</span>) Installing ncurses-terminfo-base (<span class="hljs-number">6.1</span>_p20200118-r4)
# (<span class="hljs-number">18</span>/<span class="hljs-number">21</span>) Installing ncurses-libs (<span class="hljs-number">6.1</span>_p20200118-r4)
# (<span class="hljs-number">19</span>/<span class="hljs-number">21</span>) Installing readline (<span class="hljs-number">8.0</span><span class="hljs-number">.1</span>-r0)
# (<span class="hljs-number">20</span>/<span class="hljs-number">21</span>) Installing sqlite-libs (<span class="hljs-number">3.30</span><span class="hljs-number">.1</span>-r2)
# (<span class="hljs-number">21</span>/<span class="hljs-number">21</span>) Installing python2 (<span class="hljs-number">2.7</span><span class="hljs-number">.18</span>-r0)
# Executing busybox<span class="hljs-number">-1.31</span><span class="hljs-number">.1</span>-r9.trigger
# OK: <span class="hljs-number">212</span> MiB <span class="hljs-keyword">in</span> <span class="hljs-number">37</span> packages
# Removing intermediate container <span class="hljs-number">5</span>f20a0ecc04b
#  ---&gt; <span class="hljs-number">637</span>ca797d709
# Step <span class="hljs-number">3</span>/<span class="hljs-number">14</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">846361</span>b57599
# Removing intermediate container <span class="hljs-number">846361</span>b57599
#  ---&gt; <span class="hljs-number">3</span>d58a482896e
# Step <span class="hljs-number">4</span>/<span class="hljs-number">14</span> : COPY ./package.json .
#  ---&gt; <span class="hljs-number">11</span>b387794039
# Step <span class="hljs-number">5</span>/<span class="hljs-number">14</span> : RUN npm install --only=prod
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">2e27</span>e33f935d
#  added <span class="hljs-number">269</span> packages <span class="hljs-keyword">from</span> <span class="hljs-number">220</span> contributors and audited <span class="hljs-number">1137</span> packages <span class="hljs-keyword">in</span> <span class="hljs-number">140.322</span>s
#
# <span class="hljs-number">4</span> packages are looking <span class="hljs-keyword">for</span> funding
#   run <span class="hljs-string">`npm fund`</span> <span class="hljs-keyword">for</span> details
#
# found <span class="hljs-number">0</span> vulnerabilities
#
# Removing intermediate container <span class="hljs-number">2e27</span>e33f935d
#  ---&gt; eb7cb2cb0b20
# Step <span class="hljs-number">6</span>/<span class="hljs-number">14</span> : FROM node:lts-alpine
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">7</span>/<span class="hljs-number">14</span> : EXPOSE <span class="hljs-number">3000</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">4</span>ea24f871747
# Removing intermediate container <span class="hljs-number">4</span>ea24f871747
#  ---&gt; <span class="hljs-number">1</span>f0206f2f050
# Step <span class="hljs-number">8</span>/<span class="hljs-number">14</span> : ENV NODE_ENV=production
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">5</span>d40d6ac3b7e
# Removing intermediate container <span class="hljs-number">5</span>d40d6ac3b7e
#  ---&gt; <span class="hljs-number">31</span>f62da17929
# Step <span class="hljs-number">9</span>/<span class="hljs-number">14</span> : USER node
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">0963e1</span>fb19a0
# Removing intermediate container <span class="hljs-number">0963e1</span>fb19a0
#  ---&gt; <span class="hljs-number">0</span>f4045152b1c
# Step <span class="hljs-number">10</span>/<span class="hljs-number">14</span> : RUN mkdir -p /home/node/app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>ac591b3adbd
# Removing intermediate container <span class="hljs-number">0</span>ac591b3adbd
#  ---&gt; <span class="hljs-number">5908373</span>dfc75
# Step <span class="hljs-number">11</span>/<span class="hljs-number">14</span> : WORKDIR /home/node/app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">55253</span>b62ff57
# Removing intermediate container <span class="hljs-number">55253</span>b62ff57
#  ---&gt; <span class="hljs-number">2883</span>cdb7c77a
# Step <span class="hljs-number">12</span>/<span class="hljs-number">14</span> : COPY . .
#  ---&gt; <span class="hljs-number">8e60893</span>a7142
# Step <span class="hljs-number">13</span>/<span class="hljs-number">14</span> : COPY --<span class="hljs-keyword">from</span>=builder /app/node_modules  /home/node/app/node_modules
#  ---&gt; <span class="hljs-number">27</span>a85faa4342
# Step <span class="hljs-number">14</span>/<span class="hljs-number">14</span> : CMD [ <span class="hljs-string">"node"</span>, <span class="hljs-string">"bin/www"</span> ]
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">349</span>c8ca6dd3e
# Removing intermediate container <span class="hljs-number">349</span>c8ca6dd3e
#  ---&gt; <span class="hljs-number">9</span>ea100571585
# Successfully built <span class="hljs-number">9</span>ea100571585
# Successfully tagged notes-api:latest
</code></pre><p>Before you run a container using this image, make sure the database container is running, and is attached to the <code>notes-api-network</code>.</p>
<pre><code>docker container inspect notes-db

# [
#     {
#         ...
#         <span class="hljs-string">"State"</span>: {
#             <span class="hljs-string">"Status"</span>: <span class="hljs-string">"running"</span>,
#             <span class="hljs-string">"Running"</span>: <span class="hljs-literal">true</span>,
#             <span class="hljs-string">"Paused"</span>: <span class="hljs-literal">false</span>,
#             <span class="hljs-string">"Restarting"</span>: <span class="hljs-literal">false</span>,
#             <span class="hljs-string">"OOMKilled"</span>: <span class="hljs-literal">false</span>,
#             <span class="hljs-string">"Dead"</span>: <span class="hljs-literal">false</span>,
#             <span class="hljs-string">"Pid"</span>: <span class="hljs-number">11521</span>,
#             <span class="hljs-string">"ExitCode"</span>: <span class="hljs-number">0</span>,
#             <span class="hljs-string">"Error"</span>: <span class="hljs-string">""</span>,
#             <span class="hljs-string">"StartedAt"</span>: <span class="hljs-string">"2021-01-26T06:55:44.928510218Z"</span>,
#             <span class="hljs-string">"FinishedAt"</span>: <span class="hljs-string">"2021-01-25T14:19:31.316854657Z"</span>
#         },
#         ...
#         <span class="hljs-string">"Mounts"</span>: [
#             {
#                 <span class="hljs-string">"Type"</span>: <span class="hljs-string">"volume"</span>,
#                 <span class="hljs-string">"Name"</span>: <span class="hljs-string">"notes-db-data"</span>,
#                 <span class="hljs-string">"Source"</span>: <span class="hljs-string">"/var/lib/docker/volumes/notes-db-data/_data"</span>,
#                 <span class="hljs-string">"Destination"</span>: <span class="hljs-string">"/var/lib/postgresql/data"</span>,
#                 <span class="hljs-string">"Driver"</span>: <span class="hljs-string">"local"</span>,
#                 <span class="hljs-string">"Mode"</span>: <span class="hljs-string">"z"</span>,
#                 <span class="hljs-string">"RW"</span>: <span class="hljs-literal">true</span>,
#                 <span class="hljs-string">"Propagation"</span>: <span class="hljs-string">""</span>
#             }
#         ],
#         ...
#         <span class="hljs-string">"NetworkSettings"</span>: {
#             ...
#             <span class="hljs-string">"Networks"</span>: {
#                 <span class="hljs-string">"bridge"</span>: {
#                     <span class="hljs-string">"IPAMConfig"</span>: <span class="hljs-literal">null</span>,
#                     <span class="hljs-string">"Links"</span>: <span class="hljs-literal">null</span>,
#                     <span class="hljs-string">"Aliases"</span>: <span class="hljs-literal">null</span>,
#                     <span class="hljs-string">"NetworkID"</span>: <span class="hljs-string">"e4c7ce50a5a2a49672155ff498597db336ecc2e3bbb6ee8baeebcf9fcfa0e1ab"</span>,
#                     <span class="hljs-string">"EndpointID"</span>: <span class="hljs-string">"2a2587f8285fa020878dd38bdc630cdfca0d769f76fc143d1b554237ce907371"</span>,
#                     <span class="hljs-string">"Gateway"</span>: <span class="hljs-string">"172.17.0.1"</span>,
#                     <span class="hljs-string">"IPAddress"</span>: <span class="hljs-string">"172.17.0.2"</span>,
#                     <span class="hljs-string">"IPPrefixLen"</span>: <span class="hljs-number">16</span>,
#                     <span class="hljs-string">"IPv6Gateway"</span>: <span class="hljs-string">""</span>,
#                     <span class="hljs-string">"GlobalIPv6Address"</span>: <span class="hljs-string">""</span>,
#                     <span class="hljs-string">"GlobalIPv6PrefixLen"</span>: <span class="hljs-number">0</span>,
#                     <span class="hljs-string">"MacAddress"</span>: <span class="hljs-string">"02:42:ac:11:00:02"</span>,
#                     <span class="hljs-string">"DriverOpts"</span>: <span class="hljs-literal">null</span>
#                 },
#                 <span class="hljs-string">"notes-api-network"</span>: {
#                     <span class="hljs-string">"IPAMConfig"</span>: {},
#                     <span class="hljs-string">"Links"</span>: <span class="hljs-literal">null</span>,
#                     <span class="hljs-string">"Aliases"</span>: [
#                         <span class="hljs-string">"37755e86d627"</span>
#                     ],
#                     <span class="hljs-string">"NetworkID"</span>: <span class="hljs-string">"06579ad9f93d59fc3866ac628ed258dfac2ed7bc1a9cd6fe6e67220b15d203ea"</span>,
#                     <span class="hljs-string">"EndpointID"</span>: <span class="hljs-string">"5b8f8718ec9a5ec53e7a13cce3cb540fdf3556fb34242362a8da4cc08d37223c"</span>,
#                     <span class="hljs-string">"Gateway"</span>: <span class="hljs-string">"172.18.0.1"</span>,
#                     <span class="hljs-string">"IPAddress"</span>: <span class="hljs-string">"172.18.0.2"</span>,
#                     <span class="hljs-string">"IPPrefixLen"</span>: <span class="hljs-number">16</span>,
#                     <span class="hljs-string">"IPv6Gateway"</span>: <span class="hljs-string">""</span>,
#                     <span class="hljs-string">"GlobalIPv6Address"</span>: <span class="hljs-string">""</span>,
#                     <span class="hljs-string">"GlobalIPv6PrefixLen"</span>: <span class="hljs-number">0</span>,
#                     <span class="hljs-string">"MacAddress"</span>: <span class="hljs-string">"02:42:ac:12:00:02"</span>,
#                     <span class="hljs-string">"DriverOpts"</span>: {}
#                 }
#             }
#         }
#     }
# ]
</code></pre><p>I've shortened the output for easy viewing here. On my system, the <code>notes-db</code> container is running, uses the <code>notes-db-data</code> volume, and is attached to the <code>notes-api-network</code> bridge.</p>
<p>Once you're assured that everything is in place, you can run a new container by executing the following command:</p>
<pre><code>docker container run \
    --detach \
    --name=notes-api \
    --env DB_HOST=notes-db \
    --env DB_DATABASE=notesdb \
    --env DB_PASSWORD=secret \
    --publish=<span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> \
    --network=notes-api-network \
    notes-api

# f9ece420872de99a060b954e3c236cbb1e23d468feffa7fed1e06985d99fb919
</code></pre><p>You should be able to understand this long command by yourself, so I'll go through the environment variables briefly. </p>
<p>The <code>notes-api</code> application requires three environment variables to be set. They are as follows:</p>
<ul>
<li><code>DB_HOST</code> - This is the host of the database server. Given that both the database server and the API are attached to the same user-defined bridge network, the database server can be refereed to using its container name which is <code>notes-db</code> in this case.</li>
<li><code>DB_DATABASE</code> - The database that this API will use. On <a target="_blank" href="https://www.freecodecamp.org/news/@fhsinchy/s/the-docker-handbook/~/drafts/-MS2MtB5zjVVjK3Ujaz4/containerizing-a-multi-container-javascript-application#running-the-database-server">Running the Database Server</a> we set the default database name to <code>notesdb</code> using the <code>POSTGRES_DB</code> environment variable. We'll use that here.</li>
<li><code>DB_PASSWORD</code> - Password for connecting to the database. This was also set on <a target="_blank" href="https://www.freecodecamp.org/news/@fhsinchy/s/the-docker-handbook/~/drafts/-MS2MtB5zjVVjK3Ujaz4/containerizing-a-multi-container-javascript-application#running-the-database-server">Running the Database Server</a> sub-section using the <code>POSTGRES_PASSWORD</code> environment variable.</li>
</ul>
<p>To check if the container is running properly or not, you can use the <code>container ls</code> command:</p>
<pre><code>docker container ls

# CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                    NAMES
# f9ece420872d   notes-api     <span class="hljs-string">"docker-entrypoint.s…"</span>   <span class="hljs-number">12</span> minutes ago   Up <span class="hljs-number">12</span> minutes   <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">3000</span>-&gt;<span class="hljs-number">3000</span>/tcp   notes-api
# <span class="hljs-number">37755e86</span>d627   postgres:<span class="hljs-number">12</span>   <span class="hljs-string">"docker-entrypoint.s…"</span>   <span class="hljs-number">17</span> hours ago     Up <span class="hljs-number">14</span> minutes   <span class="hljs-number">5432</span>/tcp                 notes-db
</code></pre><p>The container is running now. You can visit <code>http://127.0.0.1:3000/</code> to see the API in action.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/bonjour-mon-ami.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The API has five routes in total that you can see inside the <code>/notes-api/api/api/routes/notes.js</code> file.</p>
<p>Although the container is running, there is one last thing that you'll have to do before you can start using it. You'll have to run the database migration necessary for setting up the database tables, and you can do that by executing <code>npm run db:migrate</code> command inside the container.</p>
<h3 id="heading-how-to-execute-commands-in-a-running-container">How to Execute Commands in a Running Container</h3>
<p>You've already learned about executing commands in a stopped container. Another scenario is executing a command inside a running container.</p>
<p>For this, you'll have to use the <code>exec</code> command to execute a custom command inside a running container.</p>
<p>The generic syntax for the <code>exec</code> command is as follows:</p>
<pre><code>docker container exec &lt;container identifier&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">command</span>&gt;</span></span>
</code></pre><p>To execute <code>npm run db:migrate</code> inside the <code>notes-api</code> container, you can execute the following command:</p>
<pre><code>docker container exec notes-api npm run db:migrate

# &gt; notes-api@ db:migrate /home/node/app
# &gt; knex migrate:latest
#
# Using environment: production
# Batch <span class="hljs-number">1</span> run: <span class="hljs-number">1</span> migrations
</code></pre><p>In cases where you want to run an interactive command inside a running container, you'll have to use the <code>-it</code> flag. As an example, if you want to access the shell running inside the <code>notes-api</code> container, you can execute following the command:</p>
<pre><code>docker container exec -it notes-api sh

# / # uname -a
# Linux b5b1367d6b31 <span class="hljs-number">5.10</span><span class="hljs-number">.9</span><span class="hljs-number">-201.</span>fc33.x86_64 #<span class="hljs-number">1</span> SMP Wed Jan <span class="hljs-number">20</span> <span class="hljs-number">16</span>:<span class="hljs-number">56</span>:<span class="hljs-number">23</span> UTC <span class="hljs-number">2021</span> x86_64 Linux
</code></pre><h3 id="heading-how-to-write-management-scripts-in-docker">How to Write Management Scripts in Docker</h3>
<p>Managing a multi-container project along with the network and volumes and stuff means writing a lot of commands. To simplify the process, I usually have help from simple <a target="_blank" href="https://opensource.com/article/17/1/getting-started-shell-scripting">shell scripts</a> and a <a target="_blank" href="https://opensource.com/article/18/8/what-how-makefile">Makefile</a>. </p>
<p>You'll find four shell scripts in the <code>notes-api</code> directory. They are as follows:</p>
<ul>
<li><code>boot.sh</code> - Used for starting the containers if they already exist.</li>
<li><code>build.sh</code> - Creates and runs the containers. It also creates the images, volumes, and networks if necessary.</li>
<li><code>destroy.sh</code> - Removes all containers, volumes and networks associated with this project.</li>
<li><code>stop.sh</code> - Stops all running containers.</li>
</ul>
<p>There is also a <code>Makefile</code> that contains four targets named <code>start</code>, <code>stop</code>, <code>build</code> and <code>destroy</code>, each invoking the previously mentioned shell scripts.</p>
<p>If the container is in a running state in your system, executing <code>make stop</code> should stop all the containers. Executing <code>make destroy</code> should stop the containers and remove everything. Make sure you're running the scripts inside the <code>notes-api</code> directory:</p>
<pre><code>make destroy

# ./shutdown.sh
# stopping api container ---&gt;
# notes-api
# api container stopped ---&gt;

# stopping db container ---&gt;
# notes-db
# db container stopped ---&gt;

# shutdown script finished

# ./destroy.sh
# removing api container ---&gt;
# notes-api
# api container removed ---&gt;

# removing db container ---&gt;
# notes-db
# db container removed ---&gt;

# removing db data volume ---&gt;
# notes-db-data
# db data volume removed ---&gt;

# removing network ---&gt;
# notes-api-network
# network removed ---&gt;

# destroy script finished
</code></pre><p>If you're getting a permission denied error, than execute <code>chmod +x</code> on the scripts:</p>
<pre><code>chmod +x boot.sh build.sh destroy.sh shutdown.sh
</code></pre><p>I'm not going to explain these scripts because they're simple <code>if-else</code> statements along with some Docker commands that you've already seen many times. If you have some understanding of the Linux shell, you should be able to understand the scripts as well.</p>
<h2 id="heading-how-to-compose-projects-using-docker-compose">How to Compose Projects Using Docker-Compose</h2>
<p>In the previous section, you've learned about managing a multi-container project and the difficulties of it. Instead of writing so many commands, there is an easier way to manage multi-container projects, a tool called <a target="_blank" href="https://docs.docker.com/compose/">Docker Compose</a>.</p>
<p>According to the Docker <a target="_blank" href="https://docs.docker.com/compose/">documentation</a> -</p>
<blockquote>
<p>Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.</p>
</blockquote>
<p>Although Compose works in all environments, it's more focused on development and testing. Using Compose on a production environment is not recommended at all.</p>
<h3 id="heading-docker-compose-basics">Docker Compose Basics</h3>
<p>Go the directory where you've cloned the repository that came with this book. Go inside the <code>notes-api/api</code> directory and create a <code>Dockerfile.dev</code> file. Put the following code in it:</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># stage one</span>
<span class="hljs-keyword">FROM</span> node:lts-alpine as builder

<span class="hljs-comment"># install dependencies for node-gyp</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apk add --no-cache python make g++</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> ./package.json .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-comment"># stage two</span>
<span class="hljs-keyword">FROM</span> node:lts-alpine

<span class="hljs-keyword">ENV</span> NODE_ENV=development

<span class="hljs-keyword">USER</span> node
<span class="hljs-keyword">RUN</span><span class="bash"> mkdir -p /home/node/app</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /home/node/app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/node_modules /home/node/app/node_modules</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [ <span class="hljs-string">"./node_modules/.bin/nodemon"</span>, <span class="hljs-string">"--config"</span>, <span class="hljs-string">"nodemon.json"</span>, <span class="hljs-string">"bin/www"</span> ]</span>
</code></pre>
<p>The code is almost identical to the <code>Dockerfile</code> that you worked with in the previous section. The three differences in this file are as follows:</p>
<ul>
<li>On line 10, we run <code>npm install</code> instead of <code>npm run install --only=prod</code> because we want the development dependencies also.</li>
<li>On line 15, we set the <code>NODE_ENV</code> environment variable to <code>development</code> instead of <code>production</code>.</li>
<li>On line 24, we use a tool called <a target="_blank" href="https://nodemon.io/">nodemon</a> to get the hot-reload feature for the API.</li>
</ul>
<p>You already know that this project has two containers:</p>
<ul>
<li><code>notes-db</code> - A database server powered by PostgreSQL.</li>
<li><code>notes-api</code> - A REST API powered by Express.js</li>
</ul>
<p>In the world of Compose, each container that makes up the application is known as a service. The first step in composing a multi-container project is to define these services.</p>
<p>Just like the Docker daemon uses a <code>Dockerfile</code> for building images, Docker Compose uses a <code>docker-compose.yaml</code> file to read service definitions from.</p>
<p>Head to the <code>notes-api</code> directory and create a new <code>docker-compose.yaml</code> file. Put the following code into the newly created file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>

<span class="hljs-attr">services:</span> 
    <span class="hljs-attr">db:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-db-dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">notes-db-dev-data:/var/lib/postgresql/data</span>
        <span class="hljs-attr">environment:</span>
            <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">secret</span>
    <span class="hljs-attr">api:</span>
        <span class="hljs-attr">build:</span>
            <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">notes-api:dev</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-api-dev</span>
        <span class="hljs-attr">environment:</span> 
            <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">db</span> <span class="hljs-comment">## same as the database service name</span>
            <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">DB_PASSWORD:</span> <span class="hljs-string">secret</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./api:/home/node/app</span>
        <span class="hljs-attr">ports:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-number">3000</span><span class="hljs-string">:3000</span>

<span class="hljs-attr">volumes:</span>
    <span class="hljs-attr">notes-db-dev-data:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">notes-db-dev-data</span>
</code></pre>
<p>Every valid <code>docker-compose.yaml</code> file starts by defining the file version. At the time of writing, <code>3.8</code> is the latest version. You can look up the latest version <a target="_blank" href="https://docs.docker.com/compose/compose-file/">here</a>.</p>
<p>Blocks in an YAML file are defined by indentation. I will go through each of the blocks and will explain what they do.</p>
<ul>
<li>The <code>services</code> block holds the definitions for each of the services or containers in the application. <code>db</code> and <code>api</code> are the two services that comprise this project.</li>
<li>The <code>db</code> block defines a new service in the application and holds necessary information to start the container. Every service requires either a pre-built image or a <code>Dockerfile</code> to run a container. For the <code>db</code> service we're using the official PostgreSQL image.</li>
<li>Unlike the <code>db</code> service, a pre-built image for the <code>api</code> service doesn't exist. So we'll use the <code>Dockerfile.dev</code> file.</li>
<li>The <code>volumes</code> block defines any name volume needed by any of the services. At the time it only enlists <code>notes-db-dev-data</code> volume used by the <code>db</code> service.</li>
</ul>
<p>Now that have a high level overview of the <code>docker-compose.yaml</code> file, let's have a closer look at the individual services.</p>
<p>The definition code for the <code>db</code> service is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-db-dev</span>
    <span class="hljs-attr">volumes:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-string">db-data:/var/lib/postgresql/data</span>
    <span class="hljs-attr">environment:</span>
        <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">notesdb</span>
        <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">secret</span>
</code></pre>
<ul>
<li>The <code>image</code> key holds the image repository and tag used for this container. We're using the <code>postgres:12</code> image for running the database container.</li>
<li>The <code>container_name</code> indicates the name of the container. By default containers are named following <code>&lt;project directory name&gt;_&lt;service name&gt;</code> syntax. You can override that using <code>container_name</code>.</li>
<li>The <code>volumes</code> array holds the volume mappings for the service and supports named volumes, anonymous volumes, and bind mounts. The syntax <code>&lt;source&gt;:&lt;destination&gt;</code> is identical to what you've seen before.</li>
<li>The <code>environment</code> map holds the values of the various environment variables needed for the service.</li>
</ul>
<p>Definition code for the <code>api</code> service is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">api:</span>
    <span class="hljs-attr">build:</span>
        <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
        <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">notes-api:dev</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-api-dev</span>
    <span class="hljs-attr">environment:</span> 
        <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">db</span> <span class="hljs-comment">## same as the database service name</span>
        <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
        <span class="hljs-attr">DB_PASSWORD:</span> <span class="hljs-string">secret</span>
    <span class="hljs-attr">volumes:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">./api:/home/node/app</span>
    <span class="hljs-attr">ports:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-number">3000</span><span class="hljs-string">:3000</span>
</code></pre>
<ul>
<li>The <code>api</code> service doesn't come with a pre-built image. Instead it has a build configuration. Under the <code>build</code> block we define the context and the name of the Dockerfile for building an image. You should have an understanding of context and Dockerfile by now so I won't spend time explaining those.</li>
<li>The <code>image</code> key holds the name of the image to be built. If not assigned, the image will be named following the <code>&lt;project directory name&gt;_&lt;service name&gt;</code> syntax.</li>
<li>Inside the <code>environment</code> map, the <code>DB_HOST</code> variable demonstrates a feature of Compose. That is, you can refer to another service in the same application by using its name. So the <code>db</code> here, will be replaced by the IP address of the <code>api</code> service container. The <code>DB_DATABASE</code> and <code>DB_PASSWORD</code> variables have to match up with <code>POSTGRES_DB</code> and <code>POSTGRES_PASSWORD</code> respectively from the <code>db</code> service definition.</li>
<li>In the <code>volumes</code> map, you can see an anonymous volume and a bind mount described. The syntax is identical to what you've seen in previous sections.</li>
<li>The <code>ports</code> map defines any port mapping. The syntax, <code>&lt;host port&gt;:&lt;container port&gt;</code> is identical to the <code>--publish</code> option you used before.</li>
</ul>
<p>Finally, the code for the <code>volumes</code> is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">volumes:</span>
    <span class="hljs-attr">db-data:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">notes-db-dev-data</span>
</code></pre>
<p>Any named volume used in any of the services has to be defined here. If you don't define a name, the volume will be named following the <code>&lt;project directory name&gt;_&lt;volume key&gt;</code> and the key here is <code>db-data</code>. </p>
<p>You can learn about the different options for volume configuration in the official <a target="_blank" href="https://docs.docker.com/compose/compose-file/compose-file-v3/#volumes">docs</a>.</p>
<h3 id="heading-how-to-start-services-in-docker-compose">How to Start Services in Docker Compose</h3>
<p>There are a few ways of starting services defined in a YAML file. The first command that you'll learn about is the <code>up</code> command. The <code>up</code> command builds any missing images, creates containers, and starts them in one go.</p>
<p>Before you execute the command, though, make sure you've opened your terminal in the same directory where the <code>docker-compose.yaml</code> file is. This is very important for every <code>docker-compose</code> command you execute.</p>
<pre><code>docker-compose --file docker-compose.yaml up --detach

# Creating network <span class="hljs-string">"notes-api_default"</span> <span class="hljs-keyword">with</span> the <span class="hljs-keyword">default</span> driver
# Creating volume <span class="hljs-string">"notes-db-dev-data"</span> <span class="hljs-keyword">with</span> <span class="hljs-keyword">default</span> driver
# Building api
# Sending build context to Docker daemon  <span class="hljs-number">37.38</span>kB
#
# Step <span class="hljs-number">1</span>/<span class="hljs-number">13</span> : FROM node:lts-alpine <span class="hljs-keyword">as</span> builder
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">2</span>/<span class="hljs-number">13</span> : RUN apk add --no-cache python make g++
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">197056</span>ec1964
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">197056</span>ec1964
#  ---&gt; <span class="hljs-number">6609935</span>fe50b
# Step <span class="hljs-number">3</span>/<span class="hljs-number">13</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">17010</span>f65c5e7
# Removing intermediate container <span class="hljs-number">17010</span>f65c5e7
#  ---&gt; b10d12e676ad
# Step <span class="hljs-number">4</span>/<span class="hljs-number">13</span> : COPY ./package.json .
#  ---&gt; <span class="hljs-number">600</span>d31d9362e
# Step <span class="hljs-number">5</span>/<span class="hljs-number">13</span> : RUN npm install
#  ---&gt; Running <span class="hljs-keyword">in</span> a14afc8c0743
### LONG INSTALLATION STUFF GOES HERE ###
#  Removing intermediate container a14afc8c0743
#  ---&gt; <span class="hljs-number">952</span>d5d86e361
# Step <span class="hljs-number">6</span>/<span class="hljs-number">13</span> : FROM node:lts-alpine
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">7</span>/<span class="hljs-number">13</span> : ENV NODE_ENV=development
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>d5376a9e78a
# Removing intermediate container <span class="hljs-number">0</span>d5376a9e78a
#  ---&gt; <span class="hljs-number">910</span>c081ce5f5
# Step <span class="hljs-number">8</span>/<span class="hljs-number">13</span> : USER node
#  ---&gt; Running <span class="hljs-keyword">in</span> cfaefceb1eff
# Removing intermediate container cfaefceb1eff
#  ---&gt; <span class="hljs-number">1480176</span>a1058
# Step <span class="hljs-number">9</span>/<span class="hljs-number">13</span> : RUN mkdir -p /home/node/app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">3</span>ae30e6fb8b8
# Removing intermediate container <span class="hljs-number">3</span>ae30e6fb8b8
#  ---&gt; c391cee4b92c
# Step <span class="hljs-number">10</span>/<span class="hljs-number">13</span> : WORKDIR /home/node/app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">6</span>aa27f6b50c1
# Removing intermediate container <span class="hljs-number">6</span>aa27f6b50c1
#  ---&gt; <span class="hljs-number">761</span>a7435dbca
# Step <span class="hljs-number">11</span>/<span class="hljs-number">13</span> : COPY . .
#  ---&gt; b5d5c5bdf3a6
# Step <span class="hljs-number">12</span>/<span class="hljs-number">13</span> : COPY --<span class="hljs-keyword">from</span>=builder /app/node_modules /home/node/app/node_modules
#  ---&gt; <span class="hljs-number">9e1</span>a19960420
# Step <span class="hljs-number">13</span>/<span class="hljs-number">13</span> : CMD [ <span class="hljs-string">"./node_modules/.bin/nodemon"</span>, <span class="hljs-string">"--config"</span>, <span class="hljs-string">"nodemon.json"</span>, <span class="hljs-string">"bin/www"</span> ]
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">5</span>bdd62236994
# Removing intermediate container <span class="hljs-number">5</span>bdd62236994
#  ---&gt; <span class="hljs-number">548e178</span>f1386
# Successfully built <span class="hljs-number">548e178</span>f1386
# Successfully tagged notes-api:dev
# Creating notes-api-dev ... done
# Creating notes-db-dev  ... done
</code></pre><p>The <code>--detach</code> or <code>-d</code> option here functions the same as the one you've seen before. The <code>--file</code> or <code>-f</code> option is only needed if the YAML file is not named <code>docker-compose.yaml</code> (but I've used here for demonstration purposes).</p>
<p>Apart from the the <code>up</code> command there is the <code>start</code> command. The main difference between these two is that the <code>start</code> command doesn't create missing containers, only starts existing containers. It's basically the same as the <code>container start</code> command.</p>
<p>The <code>--build</code> option for the <code>up</code> command forces a rebuild of the images. There are some other options for the <code>up</code> command that you can see in the official <a target="_blank" href="https://docs.docker.com/compose/reference/up/">docs</a>.</p>
<h3 id="heading-how-to-list-services-in-docker-compose">How to List Services in Docker Compose</h3>
<p>Although service containers started by Compose can be listed using the <code>container ls</code> command, there is the <code>ps</code> command for listing containers defined in the YAML only.</p>
<pre><code>docker-compose ps

#     Name                   Command               State           Ports         
# -------------------------------------------------------------------------------
# notes-api-dev   docker-entrypoint.sh ./nod ...   Up      <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">3000</span>-&gt;<span class="hljs-number">3000</span>/tcp
# notes-db-dev    docker-entrypoint.sh postgres    Up      <span class="hljs-number">5432</span>/tcp
</code></pre><p>It's not as informative as the <code>container ls</code> output, but it's useful when you have tons of containers running simultaneously.</p>
<h3 id="heading-how-to-execute-commands-inside-a-running-service-in-docker-compose">How to Execute Commands Inside a Running Service in Docker Compose</h3>
<p>I hope you remember from the previous section that you have to run some migration scripts to create the database tables for this API. </p>
<p>Just like the <code>container exec</code> command, there is an <code>exec</code> command for <code>docker-compose</code>. Generic syntax for the command is as follows:</p>
<pre><code>docker-compose exec &lt;service name&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">command</span>&gt;</span></span>
</code></pre><p>To execute the <code>npm run db:migrate</code> command inside the <code>api</code> service, you can execute the following command:</p>
<pre><code>docker-compose exec api npm run db:migrate

# &gt; notes-api@ db:migrate /home/node/app
# &gt; knex migrate:latest
# 
# Using environment: development
# Batch <span class="hljs-number">1</span> run: <span class="hljs-number">1</span> migrations
</code></pre><p>Unlike the <code>container exec</code> command, you don't need to pass the <code>-it</code> flag for interactive sessions. <code>docker-compose</code> does that automatically.</p>
<h3 id="heading-how-to-access-logs-from-a-running-service-in-docker-compose">How to Access Logs from a Running Service in Docker Compose</h3>
<p>You can also use the <code>logs</code> command to retrieve logs from a running service. The generic syntax for the command is as follows:</p>
<pre><code>docker-compose logs &lt;service name&gt;
</code></pre><p>To access the logs from the <code>api</code> service, execute the following command:</p>
<pre><code>docker-compose logs api

# Attaching to notes-api-dev
# notes-api-dev | [nodemon] <span class="hljs-number">2.0</span><span class="hljs-number">.7</span>
# notes-api-dev | [nodemon] reading config ./nodemon.json
# notes-api-dev | [nodemon] to restart at any time, enter <span class="hljs-string">`rs`</span>
# notes-api-dev | [nodemon] or send SIGHUP to <span class="hljs-number">1</span> to restart
# notes-api-dev | [nodemon] ignoring: *.test.js
# notes-api-dev | [nodemon] watching path(s): *.*
# notes-api-dev | [nodemon] watching extensions: js,mjs,json
# notes-api-dev | [nodemon] starting <span class="hljs-string">`node bin/www`</span>
# notes-api-dev | [nodemon] forking
# notes-api-dev | [nodemon] child pid: <span class="hljs-number">19</span>
# notes-api-dev | [nodemon] watching <span class="hljs-number">18</span> files
# notes-api-dev | app running -&gt; http:<span class="hljs-comment">//127.0.0.1:3000</span>
</code></pre><p>This is just a portion from the log output. You can kind of hook into the output stream of the service and get the logs in real-time by using the <code>-f</code> or <code>--follow</code> option. Any later log will show up instantly in the terminal as long as you don't exit by pressing <code>ctrl + c</code> or closing the window. The container will keep running even if you exit out of the log window.</p>
<h3 id="heading-how-to-stop-services-in-docker-compose">How to Stop Services in Docker Compose</h3>
<p>To stop services, there are two approaches that you can take. The first one is the <code>down</code> command. The <code>down</code> command stops all running containers and removes them from the system. It also removes any networks:</p>
<pre><code>docker-compose down --volumes

# Stopping notes-api-dev ... done
# Stopping notes-db-dev  ... done
# Removing notes-api-dev ... done
# Removing notes-db-dev  ... done
# Removing network notes-api_default
# Removing volume notes-db-dev-data
</code></pre><p>The <code>--volumes</code> option indicates that you want to remove any named volume(s) defined in the <code>volumes</code> block. You can learn about the additional options for the <code>down</code> command in the official <a target="_blank" href="https://docs.docker.com/compose/reference/down/">docs</a>.</p>
<p>Another command for stopping services is the <code>stop</code> command which functions identically to the <code>container stop</code> command. It stops all the containers for the application and keeps them. These containers can later be started with the <code>start</code> or <code>up</code> command.</p>
<h3 id="heading-how-to-compose-a-full-stack-application-in-docker-compose">How to Compose a Full-stack Application in Docker Compose</h3>
<p>In this sub-section, we'll be adding a front-end to our notes API and turning it into a complete full-stack application. I won't be explaining any of the <code>Dockerfile.dev</code> files in this sub-section (except the one for the <code>nginx</code> service) as they are identical to some of the others you've already seen in previous sub-sections.‌</p>
<p>If you've cloned the project code repository, then go inside the <code>fullstack-notes-application</code> directory. Each directory inside the project root contains the code for each service and the corresponding <code>Dockerfile</code>.‌</p>
<p>Before we start with the <code>docker-compose.yaml</code> file let's look at a diagram of how the application is going to work:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/fullstack-application-design.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Instead of accepting requests directly like we previously did, in this application all the requests will be first received by an NGINX (lets call it router) service. </p>
<p>The router will then see if the requested end-point has <code>/api</code> in it. If yes, the router will route the request to the back-end or if not, the router will route the request to the front-end.</p>
<p>You do this because when you run a front-end application it doesn't run inside a container. It runs on the browser, served from a container. As a result, Compose networking doesn't work as expected and the front-end application fails to find the <code>api</code> service.</p>
<p>NGINX, on the other hand, runs inside a container and can communicate with the different services across the entire application.</p>
<p>I will not get into the configuration of NGINX here. That topic is kinda out of the scope of this book. But if you want to have a look at it, go ahead and check out the <code>/notes-api/nginx/development.conf</code> and <code>/notes-api/nginx/production.conf</code> files. Code for the <code>/notes-api/nginx/Dockerfile.dev</code> is as follows:</p>
<pre><code>FROM nginx:stable-alpine

COPY ./development.conf /etc/nginx/conf.d/<span class="hljs-keyword">default</span>.conf
</code></pre><p>All it does is copy the configuration file to <code>/etc/nginx/conf.d/default.conf</code> inside the container.</p>
<p>Let's start writing the <code>docker-compose.yaml</code> file. Apart from the <code>api</code> and <code>db</code> services there will be the <code>client</code> and <code>nginx</code> services. There will also be some network definitions that I'll get into shortly.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>

<span class="hljs-attr">services:</span> 
    <span class="hljs-attr">db:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-db-dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">db-data:/var/lib/postgresql/data</span>
        <span class="hljs-attr">environment:</span>
            <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">secret</span>
        <span class="hljs-attr">networks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">backend</span>
    <span class="hljs-attr">api:</span>
        <span class="hljs-attr">build:</span> 
            <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">notes-api:dev</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-api-dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./api:/home/node/app</span>
        <span class="hljs-attr">environment:</span> 
            <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">db</span> <span class="hljs-comment">## same as the database service name</span>
            <span class="hljs-attr">DB_PORT:</span> <span class="hljs-number">5432</span>
            <span class="hljs-attr">DB_USER:</span> <span class="hljs-string">postgres</span>
            <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">DB_PASSWORD:</span> <span class="hljs-string">secret</span>
        <span class="hljs-attr">networks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">backend</span>
    <span class="hljs-attr">client:</span>
        <span class="hljs-attr">build:</span>
            <span class="hljs-attr">context:</span> <span class="hljs-string">./client</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">notes-client:dev</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-client-dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./client:/home/node/app</span>
        <span class="hljs-attr">networks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">frontend</span>
    <span class="hljs-attr">nginx:</span>
        <span class="hljs-attr">build:</span>
            <span class="hljs-attr">context:</span> <span class="hljs-string">./nginx</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">notes-router:dev</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-router-dev</span>
        <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
        <span class="hljs-attr">ports:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-number">8080</span><span class="hljs-string">:80</span>
        <span class="hljs-attr">networks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">backend</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">frontend</span>

<span class="hljs-attr">volumes:</span>
    <span class="hljs-attr">db-data:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">notes-db-dev-data</span>

<span class="hljs-attr">networks:</span> 
    <span class="hljs-attr">frontend:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">fullstack-notes-application-network-frontend</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
    <span class="hljs-attr">backend:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">fullstack-notes-application-network-backend</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
</code></pre>
<p>The file is almost identical to the previous one you worked with. The only thing that needs some explanation is the network configuration. The code for the <code>networks</code> block is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">networks:</span> 
    <span class="hljs-attr">frontend:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">fullstack-notes-application-network-frontend</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
    <span class="hljs-attr">backend:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">fullstack-notes-application-network-backend</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
</code></pre>
<p>I've defined two bridge networks. By default, Compose creates a bridge network and attaches all containers to that. In this project, however, I wanted proper network isolation. So I defined two networks, one for the front-end services and one for the back-end  services.</p>
<p>I've also added <code>networks</code> block in each of the service definitions. This way the the <code>api</code> and <code>db</code> service will be attached to one network and the <code>client</code> service will be attached to a separate network. But the <code>nginx</code> service will be attached to both the networks so that it can perform as router between the front-end and back-end services.</p>
<p>Start all the services by executing the following command:</p>
<pre><code>docker-compose --file docker-compose.yaml up --detach

# Creating network <span class="hljs-string">"fullstack-notes-application-network-backend"</span> <span class="hljs-keyword">with</span> driver <span class="hljs-string">"bridge"</span>
# Creating network <span class="hljs-string">"fullstack-notes-application-network-frontend"</span> <span class="hljs-keyword">with</span> driver <span class="hljs-string">"bridge"</span>
# Creating volume <span class="hljs-string">"notes-db-dev-data"</span> <span class="hljs-keyword">with</span> <span class="hljs-keyword">default</span> driver
# Building api
# Sending build context to Docker daemon  <span class="hljs-number">37.38</span>kB
# 
# Step <span class="hljs-number">1</span>/<span class="hljs-number">13</span> : FROM node:lts-alpine <span class="hljs-keyword">as</span> builder
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">2</span>/<span class="hljs-number">13</span> : RUN apk add --no-cache python make g++
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">8</span>a4485388fd3
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">8</span>a4485388fd3
#  ---&gt; <span class="hljs-number">47</span>fb1ab07cc0
# Step <span class="hljs-number">3</span>/<span class="hljs-number">13</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> bc76cc41f1da
# Removing intermediate container bc76cc41f1da
#  ---&gt; <span class="hljs-number">8</span>c03fdb920f9
# Step <span class="hljs-number">4</span>/<span class="hljs-number">13</span> : COPY ./package.json .
#  ---&gt; a1d5715db999
# Step <span class="hljs-number">5</span>/<span class="hljs-number">13</span> : RUN npm install
#  ---&gt; Running <span class="hljs-keyword">in</span> fabd33cc0986
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container fabd33cc0986
#  ---&gt; e09913debbd1
# Step <span class="hljs-number">6</span>/<span class="hljs-number">13</span> : FROM node:lts-alpine
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">7</span>/<span class="hljs-number">13</span> : ENV NODE_ENV=development
#  ---&gt; Using cache
#  ---&gt; b7c12361b3e5
# Step <span class="hljs-number">8</span>/<span class="hljs-number">13</span> : USER node
#  ---&gt; Using cache
#  ---&gt; f5ac66ca07a4
# Step <span class="hljs-number">9</span>/<span class="hljs-number">13</span> : RUN mkdir -p /home/node/app
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">60094</span>b9a6183
# Step <span class="hljs-number">10</span>/<span class="hljs-number">13</span> : WORKDIR /home/node/app
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">316</span>a252e6e3e
# Step <span class="hljs-number">11</span>/<span class="hljs-number">13</span> : COPY . .
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">3</span>a083622b753
# Step <span class="hljs-number">12</span>/<span class="hljs-number">13</span> : COPY --<span class="hljs-keyword">from</span>=builder /app/node_modules /home/node/app/node_modules
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">707979</span>b3371c
# Step <span class="hljs-number">13</span>/<span class="hljs-number">13</span> : CMD [ <span class="hljs-string">"./node_modules/.bin/nodemon"</span>, <span class="hljs-string">"--config"</span>, <span class="hljs-string">"nodemon.json"</span>, <span class="hljs-string">"bin/www"</span> ]
#  ---&gt; Using cache
#  ---&gt; f2da08a5f59b
# Successfully built f2da08a5f59b
# Successfully tagged notes-api:dev
# Building client
# Sending build context to Docker daemon  <span class="hljs-number">43.01</span>kB
# 
# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM node:lts-alpine
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : USER node
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">4</span>be5fb31f862
# Step <span class="hljs-number">3</span>/<span class="hljs-number">7</span> : RUN mkdir -p /home/node/app
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">1</span>fefc7412723
# Step <span class="hljs-number">4</span>/<span class="hljs-number">7</span> : WORKDIR /home/node/app
#  ---&gt; Using cache
#  ---&gt; d1470d878aa7
# Step <span class="hljs-number">5</span>/<span class="hljs-number">7</span> : COPY ./package.json .
#  ---&gt; Using cache
#  ---&gt; bbcc49475077
# Step <span class="hljs-number">6</span>/<span class="hljs-number">7</span> : RUN npm install
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">860</span>a4a2af447
# Step <span class="hljs-number">7</span>/<span class="hljs-number">7</span> : CMD [ <span class="hljs-string">"npm"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"serve"</span> ]
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">11</span>db51d5bee7
# Successfully built <span class="hljs-number">11</span>db51d5bee7
# Successfully tagged notes-client:dev
# Building nginx
# Sending build context to Docker daemon   <span class="hljs-number">5.12</span>kB
# 
# Step <span class="hljs-number">1</span>/<span class="hljs-number">2</span> : FROM nginx:stable-alpine
#  ---&gt; f2343e2e2507
# Step <span class="hljs-number">2</span>/<span class="hljs-number">2</span> : COPY ./development.conf /etc/nginx/conf.d/<span class="hljs-keyword">default</span>.conf
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">02</span>a55d005a98
# Successfully built <span class="hljs-number">02</span>a55d005a98
# Successfully tagged notes-router:dev
# Creating notes-client-dev ... done
# Creating notes-api-dev    ... done
# Creating notes-router-dev ... done
# Creating notes-db-dev     ... done
</code></pre><p>Now visit <code>http://localhost:8080</code> and voilà!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/notes-application.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Try adding and deleting notes to see if the application works properly. The project also comes with shell scripts and a <code>Makefile</code>. Explore them to see how you can run this project without the help of <code>docker-compose</code> like you did in the previous section.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I would like to thank you from the bottom of my heart for the time you've spent reading this book. I hope you've enjoyed it and have learned all the essentials of Docker.</p>
<p>Apart from this one, I've written full-length handbooks on other complicated topics available for free on <a target="_blank" href="https://www.freecodecamp.org/news/author/farhanhasin/">freeCodeCamp</a>.</p>
<p>These handbooks are part of my mission to simplify hard to understand technologies for everyone. Each of these handbooks takes a lot of time and effort to write.</p>
<p>If you've enjoyed my writing and want to keep me motivated, consider leaving starts on <a target="_blank" href="https://github.com/fhsinchy/">GitHub</a> and endorse me for relevant skills on <a target="_blank" href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. I also accept sponsorship so you may consider <a target="_blank" href="https://www.buymeacoffee.com/farhanhasin">buying me a coffee</a> if you want to.</p>
<p>I'm always open to suggestions and discussions on <a target="_blank" href="https://twitter.com/frhnhsin">Twitter</a> or <a target="_blank" href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. Hit me with direct messages.</p>
<p>In the end, consider sharing the resources with others, because </p>
<blockquote>
<p>Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something. — Richard Stallman</p>
</blockquote>
<p>Till the next one, stay safe and keep learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Docker Used For? A Docker Container Tutorial for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ By Lucas Santos As a developer, you have probably heard of Docker at some point in your professional life. And you're likely aware that it has become important tech for any application developer to know.  If you have no idea of what I'm talking about... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-docker-used-for-a-docker-container-tutorial-for-beginners/</link>
                <guid isPermaLink="false">66d419afeb9db875d6dff9eb</guid>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 09 Dec 2020 17:11:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/11/georg-wolf-WAgBaYHRaL4-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Lucas Santos</p>
<p>As a developer, you have probably heard of Docker at some point in your professional life. And you're likely aware that it has become important tech for any application developer to know. </p>
<p>If you have no idea of what I'm talking about, no worries – that's what this article is for.</p>
<p>We'll go on a journey to discover what is this Docker everyone is talking about and what you can do with it. By the end, we'll also create, publish, and run our first Docker image.</p>
<p>But first, let's lay the foundation for our story. I'll be using this <a target="_blank" href="https://blog.aquasec.com/a-brief-history-of-containers-from-1970s-chroot-to-docker-2016">amazing article</a> by Rani Osnat that explains the whole history of containers in more depth. And I'll summarize it here so we can focus on the important parts.</p>
<h2 id="heading-a-little-bit-of-container-history">A Little Bit of Container History</h2>
<p>Docker is a container runtime. A lot of people think that Docker was the first of its kind, but this is not true – Linux containers have existed since the 1970s. </p>
<p>Docker is important to both the development community and container community because it made using containers so easy that everyone started doing it.</p>
<h3 id="heading-what-are-containers">What are containers?</h3>
<p>Containers, or Linux Containers, are a technology that allows us to isolate certain kernel processes and trick them into thinking they're the only ones running in a completely new computer.</p>
<p>Different from Virtual Machines, a container can share the kernel of the operating system while only having their different binaries/libraries loaded with them. </p>
<p>In other words, you don't need to have whole different OS (called <strong>guest OS</strong>) installed inside your host OS. You can have several containers running within a single OS without having several different guest OS's installed.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/11/image-166.png" alt="Image" width="600" height="400" loading="lazy">
<em>Difference between Virtual Machines and Docker Containers (Source: Docker)</em></p>
<p>This makes containers much smaller, faster, and more efficient. While a VM can take about a minute to spin up and can weigh several Gigabytes, a container weighs, on average, 400 to 600mb (the biggest ones). </p>
<p>They also take only seconds to spin up. This is mostly because they don't have to spin a whole operating system before running the process.</p>
<p>And this all began with six characters.</p>
<h3 id="heading-the-beginning-of-containers">The beginning of containers</h3>
<p>The history of containers begins in 1979 with Unix v7. At that time, I wasn't even born, and my father was 15 years old. Did containers already exist in 1979? No! </p>
<p>In 1979, the Unix version 7 introduced a system call called <a target="_blank" href="https://en.wikipedia.org/wiki/Chroot">chroot</a>, which was the very beginning of what we know today as <strong>process</strong> <strong>virtualization</strong>.</p>
<p>The <code>chroot</code> call allowed the kernel to change the apparent root directory of a process and its children. </p>
<p>In short, the process thinks it's running alone in the machine, because its file system is segregated from all other processes. This same syscall was introduced in BSD in 1982. But it was only two decades later when we had the first widespread application of it.</p>
<p>In 2000, a hosting provider was searching for better ways to manage their customers' websites, since they were all installed in the same machine and competed for the same resources. </p>
<p>This solution was called <code>jails</code>, and it was one of the first real attempts to isolate stuff at the process level. Jails allowed any FreeBSD users to partition the system into several independent, smaller systems (which are called <code>jails</code>). Each jail can have its own IP config and system config.</p>
<p>Jails were the first solution to expand the uses of <code>chroot</code> to allow not only the segregation at the filesystem level, but also virtualizing users, network, sub-systems and so on.</p>
<p>In 2008, LXC (<strong>L</strong>inu<strong>X</strong> <strong>C</strong>ontainers) was launched. It was, at the time, the first and most complete implementation of a container management system. It used control groups, namespaces, and a lot of what was built until then. The greatest advancement was that it was used straight from a Unix kernel, it didn't require any patches.</p>
<h2 id="heading-docker">Docker</h2>
<p>Finally, in 2010, Solomon Hykes and Sebastien Pahl created Docker during the Y Combinator startup incubator group. In 2011 the platform was launched. </p>
<p>Originally, Hykes started the Docker project in France as part of an internal project within dotCloud, a PaaS company that was shut down in 2016.</p>
<p>Docker didn't add much to the container runtimes at the time – the greatest contribution from Docker to the container ecosystem was the <strong>awareness</strong>. Its easy-to-use CLI and concepts democratized the use of containers to common developers, and not only to deep hacking companies that needed containers for some reason.</p>
<p>After 2013, several companies started adopting Docker as default container runtime because it standardized the use of containers worldwide. In 2013, Red Hat announced a Docker collaboration, in 2014 it was time for <a target="_blank" href="https://azure.microsoft.com/blog/microsoft-and-docker-collaborate-on-new-ways-to-deploy-containers-on-azure/?WT.mc_id=containers-11424-ludossan">Microsoft</a>, AWS, Stratoscale, and IBM.</p>
<p>In 2016, the first version of Docker for a different OS than Linux was announced. Windocks released a port of Docker's OSS project designed to run on Windows. And, by the end of the same year, <a target="_blank" href="https://azure.microsoft.com/blog/microsoft-and-docker-collaborate-on-new-ways-to-deploy-containers-on-azure/?WT.mc_id=containers-11424-ludossan">Microsoft</a> announced that Docker was now natively supported on <a target="_blank" href="https://azure.microsoft.com/blog/microsoft-and-docker-collaborate-on-new-ways-to-deploy-containers-on-azure/?WT.mc_id=containers-11424-ludossan">Windows</a> through <a target="_blank" href="https://docs.microsoft.com/virtualization/hyper-v-on-windows/about/?WT.mc_id=containers-11424-ludossan">Hyper-V</a>.</p>
<blockquote>
<p>In 2019, Microsoft announced the <a target="_blank" href="https://docs.microsoft.com/windows/wsl/install-win10?WT.mc_id=containers-11424-ludossan">WSL2</a>, which made possible for Docker to run on Windows without the need of a virtualized machine on <a target="_blank" href="https://docs.microsoft.com/virtualization/hyper-v-on-windows/about/?WT.mc_id=containers-11424-ludossan">Hyper-V</a>. Docker is now natively multiplatform while still leveraging Linux's container approach.</p>
</blockquote>
<p>Finally, in 2020, Docker became the worldwide choice for containers. This happened not necessarily because it's better than others, but because it unifies all the implementations under a single easy-to-use platform with a CLI and a Daemon. And it does all of this while using simple concepts that we'll explore in the next sections.</p>
<h2 id="heading-how-does-docker-work">How Does Docker Work?</h2>
<p>Docker packages an application and all its dependencies in a virtual container that can run on any Linux server. This is why we call them containers. Because they have all the necessary dependencies contained in a single piece of software.</p>
<p>Docker is composed of the following elements:</p>
<ul>
<li>a Daemon, which is used to build, run, and manage the containers</li>
<li>a high-level API which allows the user to communicate with the Daemon, </li>
<li>and a CLI, the interface we use to make this all available.</li>
</ul>
<h3 id="heading-docker-containers">Docker Containers</h3>
<p>Containers are abstractions of the app layer. They package all the code, libraries, and dependencies together. This makes it possible for multiple containers to run in the same host, so you can use that host's resources more efficiently.</p>
<p>Each container runs as an isolated process in the user space and take up less space than regular VMs due to their layered architecture. </p>
<p>These layers are called <strong>intermediate images</strong>, and these images are created every time you run a new command in the <code>Dockerfile</code>, for instance, if you have a Dockerfile that's like this:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:stable

<span class="hljs-keyword">COPY</span><span class="bash"> . /usr/src/app</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /usr/src/app</span>

<span class="hljs-keyword">RUN</span><span class="bash"> npm install grpc</span>

<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]</span>
</code></pre>
<p>At each command like <code>COPY</code> or <code>RUN</code> you'll be creating another layer on top of your container image. This allows Docker to split and separate each command into a separate part. So if you eventually use this <code>node:stable</code> image again, it won't need to pull all the layers of it, because you have already installed this image. </p>
<p>Also, all layers are hashed, which means Docker can cache those layers and optimize build times for layers that didn't change across builds. You won't need to rebuild and re-copy all the files if the COPY step hasn't changed, which greatly reduces the amount of time spent in build processes.</p>
<p>In the end of the build process, Docker creates a new empty layer on top of all layers called <strong>thin writable layer.</strong> This layer is the one you access when using <code>docker exec -it &lt;container&gt; &lt;command&gt;</code>. This way you can perform interactive changes in the image and commit those using <code>docker commit</code>, just like you'd do with a Git tracked file.</p>
<p>This hash-diffed layer architecture is possible because of the AuFS file system. This is a layered FS that allows files and directories to be stacked as layers one upon another. </p>
<p>AuFS pose some problems when dealing with DnD (Docker in Docker), but this is a subject for other article! You can check out a deeper explanation in <a target="_blank" href="https://medium.com/@BeNitinAgarwal/docker-containers-filesystem-demystified-b6ed8112a04a">this article</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-13.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Layers can be hash-diffed among versions. This way Docker can check if a layer has changed when building an image and decide whether to rebuild it, saving a lot of time. </p>
<p>So, if you already have the Ubuntu image downloaded on your computer, and you're building a new image which relies on one or more layers of that image, Docker won't build them again. It'll just reuse the same layers. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-14.png" alt="Image" width="600" height="400" loading="lazy">
<em>(source: Packt) Docker layers explained</em></p>
<p>If you want to dig deeper into layers, <a target="_blank" href="https://medium.com/@jessgreb01/digging-into-docker-layers-c22f948ed612">this article</a> gives a lot of detail on how to find, list, and manage them.</p>
<h3 id="heading-why-docker-containers-are-great">Why Docker containers are great</h3>
<p>You have probably heard the iconic phrase "It works on my machine". Well, why don't we give that machine to the customer? </p>
<p>That's exactly the problem Docker and containers solve in general. A Docker container is a packaged collection of all the app's libraries and dependencies already prebuilt and ready to be executed.</p>
<p>A lot of companies have migrated over from VMs to containers not only because they're much lighter and faster to spin up, but also because they're extremely easy to maintain. </p>
<p>A single container can be versioned using its <code>Dockerfile</code> (we'll get to images in the next section), so it makes quite easy for one developer (or even a small team of developers) to run and maintain a whole ecosystem of containers. On the other hand, you would need an infrastructure person just to be able to run and housekeep VMs.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-68.png" alt="Image" width="600" height="400" loading="lazy">
<em>(source: Docker) Your Datacenter with VMs and containers</em></p>
<p>Does this mean that we don't needVMs anymore? No, on the contrary, VMs are still very much needed if you want to have a whole operating system for each customer or just need the whole environment as a sandbox. VMs are usually used as middle layers when you have a big server rack and several customers that'll be using it.</p>
<p>The ease of use and maintainability leads us to another important aspect of why containers are so great: it's way cheaper for a company to use containers than fully fledged VMs. </p>
<p>This is not because the infrastructure or hardware is cheaper, but because you need fewer people to housekeep containers, which means you can better organize your team to focus on the product instead of focusing on housekeeping.</p>
<p>Still related to savings, a single medium-sized VM can run about 3 to 8 containers. It depends on how many resources your containers use and how much of the underlying OS it needs to boot before running the whole application. </p>
<p>Some languages, like Go, allow you to build an image with only the compiled binary and nothing else. This means the Docker container will have much less to load and therefore will use fewer resources. This way you can spin up more containers per VM and use your hardware more efficiently.</p>
<p>Since containers are made to be ephemeral, this means all data inside them is lost when the container is deleted. This is great, because we can use containers for burstable tasks like CI. </p>
<p>The use of containers has brought a whole new level of DevOps advancements. Now you can simply spin up lots of containers, each one doing one small step of your deployment pipeline, and then just kill them without worrying if you've left something behind. </p>
<p>The stateless nature of containers makes them the perfect tool for fast workloads.</p>
<p>Now that we've seen how containers are awesome, let's understand how we can build one of them!</p>
<h2 id="heading-what-are-docker-images">What are Docker Images?</h2>
<p>Docker images are instructions written in a special file called a <code>Dockerfile</code>. It has its own syntax and defines what steps Docker will take to build your container.</p>
<p>Since containers are only layers upon layers of changes, each new command you create in a Docker image will create a new layer in the container. </p>
<p>The last layer is what we call a <strong>thin writable layer</strong>. An empty layer which can be changed by the user and committed using the <code>docker commit</code> command.</p>
<p>This is an example of a simple image for a Node.js application:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:stable
<span class="hljs-keyword">COPY</span><span class="bash"> . /usr/src/app/</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install &amp;&amp; npm run build</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>
<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]</span>
</code></pre>
<p>In this simple example, we're creating a new image. All images are based on an existing image, or a scratch image (which I explain on my blog articles in Portuguese, <a target="_blank" href="https://blog.lsantos.dev/um-mergulho-em-imagens-de-containers-parte-1/">here</a>, <a target="_blank" href="https://blog.lsantos.dev/um-mergulho-em-imagens-de-containers-parte-2/">here</a>, and <a target="_blank" href="https://blog.lsantos.dev/um-mergulho-em-imagens-de-containers-parte-3/">here</a>). </p>
<p>These images are downloaded from a <strong>Container Registry</strong>, a repository for storing images of containers. The most common of them is the <a target="_blank" href="https://hub.docker.com/">Docker Hub</a>, but you can also create a private one using cloud solutions like <a target="_blank" href="https://azure.microsoft.com/services/container-registry/?WT.mc_id=containers-11424-ludossan">Azure Container Registry</a>.</p>
<p>When you run <code>docker build .</code> on the same directory as the Dockerfile, Docker daemon will start building the image and packaging it so you can use it. Then you can run <code>docker run &lt;image-name&gt;</code> to start a new container.</p>
<p>Notice that we expose certain ports in the Dockerfile. Docker allows us to separate networks within our own OS, which means you can map ports from your computer to the container and vice-versa. Also, you can execute commands inside containers with <code>docker exec</code>. </p>
<p>Let's put this knowledge into practice.</p>
<h2 id="heading-how-to-deploy-your-dockerized-application">How to Deploy your Dockerized Application</h2>
<p>This will be a simple and easy walkthrough on how to create a basic Docker image using a Node.js server and make it run on your computer.</p>
<p>First, start a new project in a directory of your choosing, and run <code>npm init -y</code> to create a new <code>package.json</code> file. Now let's create another directory called <code>src</code>. In this directory we'll create a new file called <code>server.js</code>.</p>
<p>Now, in your <code>package.json</code> file, change the <code>main</code> key to <code>src/server.js</code>. Also, delete the <code>test</code> script that was created and replace it with <code>"start": "node src/server.js"</code>. Your file should be like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"your-project"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"src/server.js"</span>,
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node src/server.js"</span>
  },
  <span class="hljs-attr">"keywords"</span>: [],
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"ISC"</span>
}
</code></pre>
<p>Now, create a file called <code>Dockerfile</code> (no extension). Let's write our image!</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:lts-alpine
<span class="hljs-keyword">COPY</span><span class="bash"> . /usr/src/app/</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /usr/src/app</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8089</span>
<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]</span>
</code></pre>
<p>Let's explain this:</p>
<ol>
<li>First, we're getting the node image from Docker Hub. Since images are saved by their names, we differentiate images by their tags. You can check all tags <a target="_blank" href="https://hub.docker.com/_/node">here</a>.</li>
<li>Next, we use <code>COPY</code> to copy all files in the current dir (using <code>.</code>) to a new dir in the container called <code>/usr/src/app</code>. The directory is created automatically. This is necessary because we need all our application files in there.</li>
<li>Now we change our start directory to the <code>/usr/src/app</code> directory, so we can run things from the root directory of our application.</li>
<li>We expose our port,</li>
<li>And we say that, as soon as our container runs, we'll execute "npm start".</li>
</ol>
<p>Let's build the image by running <code>docker build . -t simple-node-image</code>. This way we'll tag our image and give it a name.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-15.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You'll see that it's going to create and download the image, along with all the necessary layers. Let's run this image with the following command:</p>
<pre><code class="lang-bash">docker run -p 80:8089 simple-node-image
</code></pre>
<p>We're mapping our port <code>80</code> to the port <code>8089</code> inside the container. We can check that by typing <code>docker ps</code> like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-17.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now try to access <code>localhost:80</code>, and see what happens:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-18.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-what-is-docker-used-for">What is Docker Used For?</h2>
<p>Now that we've seen how to build a Docker container, let's jump into some practical uses of Docker and how you can get the most out of it.</p>
<h3 id="heading-ephemeral-databases">Ephemeral databases</h3>
<p>Have you ever tried to develop an application that requires a database to run? Or worse, tried to run someone else's application that needs a database that you don't have installed? </p>
<p>The old solution was to install the database first, then run the application. With Docker you just need to run the database container. Let's run a simple MongoDB container:</p>
<pre><code class="lang-bash">$ docker run -p 27017:27017 --name my-ephemeral-db mongo
</code></pre>
<p>That's it! Now you can access your database from your computer through port 27017, just like you'd do normally.</p>
<h3 id="heading-persistent-databases">Persistent databases</h3>
<p>The problem with the previous example is that, if you remove the container, all your data will be lost. So, what happens if you want to run a local database without needing to install it, but keep the data after it's deleted? You can bind Docker to a volume!</p>
<p>When you bind Docker to a local volume, you're essentially mounting your filesystem into the container or vice-versa. Let's see:</p>
<pre><code class="lang-bash">$ docker run -p 27017:27017 -v /home/my/path/to/db:/data/db --name my-persistent-db mongo
</code></pre>
<p>In this command we're mounting <code>/data/db</code> into <code>/home/my/path/to/db</code>. Now if we use <code>docker stop my-persistent-db</code> and <code>docker rm my-persistent-db</code> all our data will continue to be stored there. </p>
<p>Later, if we need the database again, we can mount it using the same command, and all the data will be back.</p>
<h3 id="heading-one-use-tools">One-use tools</h3>
<p>Another thing that all devs do: we install applications that we only use once. For example, that simple CLI to access that old database, or that simple GUI to some CI server. </p>
<p>Many tools already have Docker containers, and you can use them like this, so you don't have to install yet another tool in your notebook.</p>
<p>The best example is Redis. It has the <code>redis-cli</code> built in another container, so you don't need to install the <code>redis-cli</code> in your shell if you hardly use it. </p>
<p>Let's imagine you spin up a Redis instance with <code>docker run -d --name redis redis --bind 127.0.0.1</code> bound to the localhost interface. You can access it through another container using:</p>
<pre><code class="lang-bash">$ docker run --rm -it --network container:redis redis-cli -h 127.0.0.1
</code></pre>
<p>The <code>--rm</code> flag tells Docker that it should remove the container as soon as it's stopped, while the <code>-it</code> flags tell it we want an interactive session (with a shell) and we'll need a TTY.</p>
<h3 id="heading-run-entire-stacks">Run entire stacks</h3>
<p>If you need to test an app that relies on another app, how would you do it? Docker makes it easy by providing <code>docker-compose</code>. It's another tool in your toolbox that allows you to code a <code>docker-compose.yml</code> file which describes your environment. </p>
<p>The file looks like this:</p>
<pre><code class="lang-yml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">web:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5000:5000"</span>

  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">"redis:alpine"</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"6379:6379"</span>
</code></pre>
<p>As you can see, we're defining two services, one is called <code>web</code> and runs <code>docker build</code> on the <code>web.build</code> path. That's a <code>Dockerfile</code>. </p>
<p>After that, it exposes port <code>5000</code> both in the host and in the container. The other service is <code>redis</code>, which pulls and runs the <code>redis</code> image on port <code>6379</code>. </p>
<p>The best part is that the network layer is shared, in other words, you can access <code>redis</code> from the <code>web</code> service by simply typing <code>redis</code> and the port.</p>
<p>You can start this file with a simple <code>docker-compose up</code>, and see the magic happening. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That's it! This is the history of Docker, how it came to be, and how it works in 3000 words. I hope you liked it, and I hope this has made your advancement with Docker a bit easier.</p>
<p>As you could see, most uses of Docker are to make life easier for devs when developing applications. But there are many other uses, such as infrastructure layers and making the housekeeping of your apps a lot easier.</p>
<p>If you ever want to reach out to me, just ping me on any of my social networks on <a target="_blank" href="https://lsantos.dev">my website</a>.</p>
<p>_Cover photo by <a target="_blank" href="https://unsplash.com/@georgewolf?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Georg Wolf</a> on <a target="_blank" href="https://unsplash.com/s/photos/whale?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a>_</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Enable Live-reload on Docker-based Applications with Docker Volumes ]]>
                </title>
                <description>
                    <![CDATA[ By Erick Wendel In this post you'll learn how to configure a development environment with live-reload enabled. This will allow you to convert a legacy application so it uses Docker, Docker volumes, and docker-compose. Some developers turn up their no... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-enable-live-reload-on-docker-based-applications/</link>
                <guid isPermaLink="false">66d45e3ca3a4f04fb2dd2e3f</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 25 Jun 2020 17:16:31 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9a0f740569d1a4ca233c.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Erick Wendel</p>
<p>In this post you'll learn how to configure a development environment with live-reload enabled. This will allow you to convert a legacy application so it uses Docker, Docker volumes, and docker-compose.</p>
<p>Some developers turn up their noses when talking about using Docker for their development environment. They say that Docker isn't good for development because it always needs to rebuild the entire image to reflect all new modifications. This makes it unproductive and slow. </p>
<p>In this article, our goal is to tackle this mindset by demonstrating how simple configurations can result in many benefits such as a reliable environment over production and development environments.</p>
<p>By the end of this post you will have learned how to:</p>
<ul>
<li>Convert a legacy application to run within a Docker container;</li>
<li>Enable dependency caching on Node.js modules;</li>
<li>Enable live-reload by using docker volumes;</li>
<li>Aggregate all services within docker-compose.</li>
</ul>
<h2 id="heading-requirements"><strong>Requirements</strong></h2>
<p>In the next steps, you'll clone an existing project to execute all examples in this article. Before starting to code make sure you have the following tools installed on your machine:</p>
<ul>
<li><a target="_blank" href="https://docs.docker.com/desktop/">Docker</a> and <a target="_blank" href="https://docs.docker.com/compose/">Docker compose</a></li>
<li><a target="_blank" href="https://nodejs.org/en/download/current/">Node.js 1</a>0+</li>
<li><a target="_blank" href="https://code.visualstudio.com/download">Git</a></li>
</ul>
<h2 id="heading-why-use-docker"><strong>Why use docker?</strong></h2>
<p>More and more cutting-edge technologies are being released for the internet all the time. They're stable, and they're fun to develop and release, but they're not predictable when working over different environments. So developers created Docker to help reduce the chances of possible errors.</p>
<p>Docker is one of my favorite tools that I work with every day on desktop, web, and IoT apps. It has given me the power to not only move applications through different environments, but also to keep my local environment as clean as possible.  </p>
<p>Developers working with cutting-edge technologies are always working with something new. But what about legacy applications? Should we just rewrite everything with new tech? I know this is not as simple as it seems. We should work on new stuff, but also make improvements to existing applications. </p>
<p>Let's say you have decided to move from Windows Servers to Unix servers. How would you do it? Do you know every dependency your app requires to work?</p>
<h2 id="heading-what-should-a-development-environment-look-like">What should a development environment look like?</h2>
<p>Developers have always tried to be more productive by adding plugins, boilerplates, and codebases on their editors/IDEs/terminals. The best environment in my opinion should be:</p>
<ol>
<li>Easy to run and test;</li>
<li>Environment agnostic;</li>
<li>Fast to evaluate modifications;</li>
<li>Easy to replicate on any machine.</li>
</ol>
<p>Following these principles, we'll configure an application over the next sections of this article. Also, if you've never heard about live-reload (or hot reload), it is a feature that watches for changes in your code and restarts the server if needed. So you don't need to go back and forth, restarting your app or even rebuilding the system.</p>
<h2 id="heading-getting-started">Getting started</h2>
<p>First, you'll need to have an empty folder called <code>post-docker-livereload</code> which you'll use as a workspace. Go to the <a target="_blank" href="https://github.com/ErickWendel/nodejs-with-mongodb-api-example">Github repository</a> and clone it on your post-docker-live-reload folder.</p>
<p>Secondly, let's analyse what the application requires. If you take a look at the README.md file, there are a few instructions demonstrating how to run this app as shown in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screen-Shot-2020-06-24-at-18.10.43-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>It requires Node.js version 10 or higher and MongoDB. Instead of installing MongoDB on your local environment machine, you'll install it using Docker. You'll also expose it on localhost:27017 so applications that are not running through Docker can access it without knowing the internal Docker IP address. </p>
<p>Copy the command below and paste it in your terminal:</p>
<pre><code class="lang-bash">docker run --name mongodb -p 27017:27017 -d mongo:4
</code></pre>
<p>Using the command above, it'll download and run the MongoDB instance. Notice that if you already have an instance with this name it'll throw an error about invalid name. </p>
<p>If you see the error, run <code>docker rm mongodb</code> and it will remove any previous instance so you can run the docker run command again.</p>
<h2 id="heading-digging-into-the-application">Digging into the application</h2>
<p>The README.md file says that you need a MongoDB instance running before starting your app, along with Node.js. </p>
<p>If you have Node.js installed, go to the <code>nodejs-with-mongodb-api-example</code> folder and run the following commands:</p>
<pre><code class="lang-bash">npm i 
npm run build 
npm start
</code></pre>
<p>After running these commands, you can go to a browser on <a target="_blank" href="http://localhost:3000">http://localhost:3000</a> and see the application running as shown in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/01-start.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Keep in mind that the app already has a command to enable live reload which is <code>npm run dev:watch</code>. The pipeline should reflect the following steps:</p>
<ol>
<li>Developer changes Typescript files;</li>
<li>Typescript transpiles files to Javascript;</li>
<li>Server notices changes to Javascript and restarts the Node.js server.</li>
</ol>
<p>So mirroring files to Docker containers will reflect all changes in the container. The <code>npm run build:watch</code> from the application will catch the changes and generate output files in the lib folder so the <code>npm run dev:run</code> will restart the server every time it has been triggered.</p>
<h2 id="heading-dockerizing-applications">Dockerizing applications</h2>
<p>If Docker is a completely new world to you, don't be afraid! You will configure it from scratch. You'll need to create a few files to start:</p>
<ol>
<li><code>Dockerfile</code> - a receipt file that lists how to install and run your app;</li>
<li><code>.dockerignore</code> - a file that lists what file(s) won't go within the Docker container instance.</li>
</ol>
<h3 id="heading-creating-the-dockerfile">Creating the Dockerfile</h3>
<p>The Dockerfile is the key concept here. There you specify the steps and dependencies to prepare and run the application. As long as you have read the README.md file, it will be easy to implement the receipt file. </p>
<p>I'm going to put the whole file below and dig into it later. In your <code>nodejs-with-mongodb-api-example</code> folder create a <code>Dockerfile</code> file and paste the code below:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>-alpine

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /src</span>

<span class="hljs-keyword">ADD</span><span class="bash"> package.json /src </span>

<span class="hljs-keyword">RUN</span><span class="bash"> npm i --silent</span>

<span class="hljs-keyword">ADD</span><span class="bash"> . /src </span>

<span class="hljs-keyword">RUN</span><span class="bash"> npm run build </span>

<span class="hljs-keyword">CMD</span><span class="bash"> npm start</span>
</code></pre>
<p>What's happening there?</p>
<ul>
<li>On line 1 - It uses as its image base Node.js 14 - alpine version;</li>
<li>From lines 2 to 4 - It copies and installs Node.js dependencies from host to container. Note that the order there is important. Adding package.json to the src folder before restoring dependencies will cache it and prevent it from installing packages every time you need to build your image;</li>
<li>From lines 6 to 7 - It runs commands for the compilation process and then for starting the program as mentioned in the README.md file.</li>
</ul>
<h3 id="heading-ignoring-unnecessary-files-with-dockerignore">Ignoring unnecessary files with .dockerignore</h3>
<p>Also, I'm working on an OSX-based system and the Docker container will run on a Linux Alpine-based system. When you run <code>npm install</code> it will restore dependencies for specific environments. </p>
<p>You will now create a file to ignore the generated code from your local machine such as node<em>modules and lib</em>._ So when you copy all files from the current directory to the container it won't get invalid package versions. </p>
<p>In the <code>nodejs-with-mongodb-api-example</code> folder create a <code>.dockerignore</code> file and paste the code below:</p>
<pre><code class="lang-txt">node_modules/
lib/
</code></pre>
<h3 id="heading-building-the-docker-image">Building the docker image</h3>
<p>I prefer running this app from the rootFolder. Go back to the <code>post-docker-live-reload</code> folder and run the following commands to prepare an image for further use:</p>
<pre><code class="lang-shell">docker build -t app nodejs-with-mongodb-api-example
</code></pre>
<p>Notice that the command above uses the <code>-t</code> flag to tell you the image name and, right after that, the folder which contains the <code>Dockerfile</code> file.</p>
<h3 id="heading-working-with-volumes">Working with volumes</h3>
<p>Before running the app, let's do a few hacks to improve our experience in the Docker containers.</p>
<p>Docker volumes is a feature to mirror files through your local machine and Docker environment. You can also share volumes over containers and reuse them to cache dependencies.</p>
<p>Your goal is to watch any changes on local <code>.ts</code> files and mirror those changes in the container. Even though the files and the <code>node_modules</code> folder are in the same path. </p>
<p>Do you remember when I said that the dependencies in each operating system would be different? To make sure our local environment won't affect the Docker environment when mirroring files, we'll isolate the container's <code>node_modules</code> folder on a distinct volume. </p>
<p>Consequently, when creating the <code>node_modules</code> folder on the container, it won't create the folder on local machine environment. Run the command below in your terminal to create it:</p>
<pre><code>docker volume create --name nodemodules
</code></pre><h3 id="heading-running-and-enabling-live-reload">Running and enabling live-reload</h3>
<p>As you know, the <code>npm run dev:watch</code> specified in the README.md shows how to enable live-reload. The problem is that you're coding on a local machine and it must reflect directly your container. </p>
<p>By running the following commands you will link your local environment with the Docker container so any change in <code>nodejs-with-mongodb-api-example</code> will affect the container's <code>src</code> folder.</p>
<pre><code class="lang-shell">docker run \
    --name app \
    --link mongodb \
    -e MONGO_URL=mongodb \
    -e PORT=4000 \
    -p 4000:4000 \
    -v `pwd`/nodejs-with-mongodb-api-example:/src \
    -v nodemodules:/src/node_modules \
    app npm run dev:watch
</code></pre>
<p>Let's dig into what's happening there:</p>
<ul>
<li><code>--link</code> is giving permission to the app to access the MongoDB instance;</li>
<li><code>-e</code> - is the environment variables. As mentioned in the README.md file you can specify the MongoDB connection string you want to connect to by overriding the <code>MONGO_URL</code> variable. Override the <code>PORT</code> variable if you want to run it on a different port. Note that the value <code>mongodb</code> is the same name we used to create our MongoDB instance in the previous sections. This value is also an alias for the internal MongoDB instance's IP;</li>
<li><code>-v</code> - maps the current directory to the Docker container by using a virtual volume. Using the <code>pwd</code> command you can get the absolute path for your current working directory and then the folder you want to mirror on the Docker container. There's the <code>:/src</code>. The <code>src</code> path is the <code>WORKDIR</code> instruction defined on <code>Dockerfile</code> so we mirror the local folder to the Docker container's src;</li>
<li><code>-v</code> - the second volume there will mirror the individual volume we created on the container's <code>node_modules</code> folder;</li>
<li><code>app</code> - the image name;</li>
<li><code>npm run dev:watch</code> - this last command will override the <code>CMD</code> instruction from the <code>Dockerfile</code>.</li>
</ul>
<p>After running the command below, you can trigger the browser after changing the local <code>index.ts</code> file to see the results. The video below demonstrates these steps in practice:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/O9vEQhU_JEM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-wrapping-up">Wrapping up</h2>
<p>You know that working with shell commands works. But is not that common to use them here, and it's not productive for running all those commands, building images, and managing instances by hand. So compose it!</p>
<p>Docker compose is a way to simplify the aggregation and linking of services. You can specify the databases, logs, application, volumes, networks, and so on.</p>
<p>First, you need to remove all active instances to avoid conflict on exposed ports. Run the following commands on your terminal to remove volumes, services and containers:</p>
<pre><code class="lang-bash">docker rm app 
docker volume rm nodemodules
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)
</code></pre>
<h3 id="heading-the-docker-compose-file">The docker-compose file</h3>
<p>Create a <code>docker-compose.yml</code> file on your <code>post-docker-livereload</code> folder using the data below:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span>
<span class="hljs-attr">services:</span>
    <span class="hljs-attr">mongodb:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">mongo:4</span>
        <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-number">27017</span><span class="hljs-string">:27017</span>
    <span class="hljs-attr">app:</span>
        <span class="hljs-attr">build:</span> <span class="hljs-string">nodejs-with-mongodb-api-example</span>
        <span class="hljs-attr">command:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">dev:watch</span>
        <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-number">4000</span><span class="hljs-string">:4000</span>
        <span class="hljs-attr">environment:</span> 
            <span class="hljs-attr">MONGO_URL:</span> <span class="hljs-string">mongodb</span>
            <span class="hljs-attr">PORT:</span> <span class="hljs-number">4000</span>
        <span class="hljs-attr">volumes:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./nodejs-with-mongodb-api-example:/src/</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">nodemodules:/src/node_modules</span>
        <span class="hljs-attr">links:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">mongodb</span>
        <span class="hljs-attr">depends_on:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">mongodb</span>

<span class="hljs-attr">volumes:</span>
    <span class="hljs-attr">nodemodules:</span> {}
</code></pre>
<p>The file above specifies resources by sections. Notice that you have <code>links</code> and <code>depends_on</code> sections there. The links field is the same flag you've used in your shell command. The <code>depends_on</code> will make sure that the MongoDB is a dependency for running your app. It'll run the MongoDB before your app like magic! </p>
<p>Going back to your terminal, to start all services and build the Docker image and create volumes and link services run the following command:</p>
<pre><code class="lang-shell">docker-compose up --build
</code></pre>
<p>If you need to remove all services created before by that <code>Dockerfile</code> you can also run <code>docker-compose down</code>.</p>
<h2 id="heading-docker-is-your-friend">Docker is your friend!</h2>
<p>That's right, my friend. Docker can help you prevent many possible errors before they happen. You can use it for front and back end applications. Even for IoT when you need to control hardware, you can specify policies for using it.</p>
<p>As your next steps, I highly recommend that you take a look at container orchestrators such as Kubernetes and Docker swarm. They could improve even more your existing applications and help you go to the next level.</p>
<h2 id="heading-thank-you-for-reading"><strong>Thank you for reading</strong></h2>
<p>I really appreciate the time we spent together. I hope this content will be more than just text. I hope it will help make you a better thinker and also a better programmer. Follow me on <a target="_blank" href="https://twitter.com/erickwendel_">Twitter</a> and check out my <a target="_blank" href="https://erickwendel.com/">personal blog</a> where I share all my valuable content.</p>
<p>See ya! ?</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Docker Exec - How to Run a Command Inside a Docker Image or Container ]]>
                </title>
                <description>
                    <![CDATA[ By Jillian Rowe I'm going to let you in on a DevOps secret here: The thing all DevOpsy people love to do is build a super fancy and complex system, then find a way to deal with it like a regular shell. Or connect to it with SSH and then ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-exec-how-to-run-a-command-inside-a-docker-image-or-container/</link>
                <guid isPermaLink="false">66d45f65052ad259f07e4afe</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ docker image ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 04 Apr 2020 14:49:12 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/antoine-petitteville-hHntcuiLbOg-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jillian Rowe</p>
<p>I'm going to let you in on a DevOps secret here: The thing all DevOpsy people love to do is build a super fancy and complex system, then find a way to deal with it like a regular shell. Or connect to it with SSH and then treat it like a regular shell.</p>
<p>Docker is no different! You are running a computer inside some other computer. Maybe that computer is an EC2 instance or a laptop. You can even get really crazy and run a VM that is then running Docker.</p>
<p>Most of the time when I use Docker I am using it to package and distribute an application. Sometimes I'm using it for something cooler like a distributed computing project. But a lot of times I'm throwing a Dockerfile in a GitHub repo so that I don't have to install CLIs that I just know will eventually conflict on my laptop.</p>
<p>Long story short, you can tell Docker to run the command <code>bash</code>, which drops you into a shell:</p>
<pre><code class="lang-bash">docker run -it name-of-image bash
<span class="hljs-comment"># docker run -it continuumio/miniconda3:latest bash</span>
<span class="hljs-comment"># docker run -it node:latest bash</span>
</code></pre>
<p>But keep reading for more. ;-)</p>
<h2 id="heading-try-it-out">Try it out</h2>
<p>Google your favorite programming language's Docker up. For me this is Python, and specifically I like conda. Then run a few commands to make sure that you are in fact in that shell.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># From Host</span>
<span class="hljs-built_in">echo</span> $(<span class="hljs-built_in">pwd</span>)
<span class="hljs-comment"># Drop into docker shell</span>
docker run -it continuumio/miniconda3:latest bash
<span class="hljs-comment"># Now you are in the docker shell!</span>
<span class="hljs-built_in">echo</span> $(<span class="hljs-built_in">pwd</span>)
<span class="hljs-built_in">echo</span> <span class="hljs-variable">$USER</span>
</code></pre>
<p>Cool, huh? This is perfect for debugging a container that absolutely should be working properly. It's also great for my most common "I don't want to install this to my computer" use case. </p>
<h2 id="heading-debug-a-docker-build-with-docker-run">Debug a Docker Build with Docker Run</h2>
<p>Treating your Docker image like a regular shell will come in handy when trying to debug Docker builds. </p>
<p>Let's say you have a Dockerfile for an image you are trying to build. Normally what happens is that when running <code>docker build -t my-image .</code> (-t is for tag) Docker will run through each of your RUN steps, and stop when it gets to a command that does not exit properly. </p>
<p>In a UNIX shell, the exit code 0 means that all is well with a command. So to illustrate this point I have made our Dockerfile have a RUN command that exits with 1. </p>
<pre><code class="lang-bash">FROM continuumio/miniconda3:latest

RUN apt-get update -y; \
    apt-get upgrade -y; \
    apt-get install -y \
    vim-tiny vim-athena build-essential

RUN  conda update conda \
    &amp;&amp; conda clean --all --yes

RUN <span class="hljs-built_in">exit</span> 1
</code></pre>
<pre><code class="lang-bash">docker build -t my-image .
</code></pre>
<p>This will get you an output that looks like: </p>
<pre><code class="lang-bash">(base) ➜  my-image docker build -t my-image .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM continuumio/miniconda3:latest
 ---&gt; 406f2b43ea59
Step 2/4 : RUN apt-get update -y;     apt-get upgrade -y;     apt-get install -y     vim-tiny vim-athena build-essential
 ---&gt; Using cache
 ---&gt; 726af29a48a0
Step 3/4 : RUN  conda update conda     &amp;&amp; conda clean --all --yes
 ---&gt; Using cache
 ---&gt; 19478bb3ce67
Step 4/4 : RUN <span class="hljs-built_in">exit</span> 1
 ---&gt; Running <span class="hljs-keyword">in</span> 7c98aab6b52c
The <span class="hljs-built_in">command</span> <span class="hljs-string">'/bin/sh -c exit 1'</span> returned a non-zero code: 1
</code></pre>
<p>You can confirm that your Docker image wasn't built by running <code>docker images</code> and checking for <code>my-image</code>. It won't be there because it wasn't successfully built. </p>
<p>Now what we can do is to comment out that troublesome Dockerfile RUN command.</p>
<pre><code class="lang-bash">FROM continuumio/miniconda3:latest

RUN apt-get update -y; \
    apt-get upgrade -y; \
    apt-get install -y \
    vim-tiny vim-athena build-essential

RUN  conda update conda \
    &amp;&amp; conda clean --all --yes

<span class="hljs-comment">#RUN exit 1</span>
</code></pre>
<p>Then what you will see is:</p>
<pre><code class="lang-bash">Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM continuumio/miniconda3:latest
 ---&gt; 406f2b43ea59
Step 2/3 : RUN apt-get update -y;     apt-get upgrade -y;     apt-get install -y     vim-tiny vim-athena build-essential
 ---&gt; Using cache
 ---&gt; 726af29a48a0
Step 3/3 : RUN  conda update conda     &amp;&amp; conda clean --all --yes
 ---&gt; Using cache
 ---&gt; 19478bb3ce67
Successfully built 19478bb3ce67
Successfully tagged my-image:latest
</code></pre>
<p>You can now drop into your Docker image and start interactively running commands!</p>
<pre><code class="lang-bash">docker run -it my-image bash
<span class="hljs-comment"># you can also run</span>
<span class="hljs-comment"># docker run -it my-image:latest bash</span>
</code></pre>
<p>From here, one by one, you can start debugging your RUN commands to see what went wrong. If you're not sure if a command exited properly or not, run <code>$?</code>:</p>
<pre><code># First run docker run -it my-image bash to get to the shell
# Print the string hello
echo <span class="hljs-string">"hello"</span>
# hello
echo $?
# <span class="hljs-number">0</span>

# Run a non existant command hello
$(hello)
# bash: hello: command not found
echo $?
# <span class="hljs-number">127</span>
</code></pre><p>You can keep running these steps, commenting out your Dockerfile, dropping into a shell, and figuring out problematic commands, until your Docker images builds perfectly.</p>
<h2 id="heading-wrap-up">Wrap Up</h2>
<p>Hopefully I have shown you that using a Docker image is no different from the terminal on your computer. Using Docker images is an awesome way to distribute applications. </p>
<p>Try to take your favorite CLI application or next GitHub project, and instead of creating an install script, package it up with Docker. ;-)</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 7 Cases When You Should Not Use Docker ]]>
                </title>
                <description>
                    <![CDATA[ Docker is a game-changer. But it is not a one-size-fits-all solution. There are many good things about Docker. It packs, ships, and runs applications as a lightweight, portable, and self-sufficient containerization tool. Docker is great for businesse... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/7-cases-when-not-to-use-docker/</link>
                <guid isPermaLink="false">66be14a82969640c348b568b</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oleh Romanyuk ]]>
                </dc:creator>
                <pubDate>Tue, 19 Nov 2019 16:23:17 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/11/Docker-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-docker-is-a-game-changer-but-it-is-not-a-one-size-fits-all-solution">Docker is a game-changer. But it is not a one-size-fits-all solution.</h2>
<p>There are many good things about Docker. It packs, ships, and runs applications as a lightweight, portable, and self-sufficient containerization tool. Docker is great for businesses of all sizes. When you are working on a piece of code in a small team, it eliminates the “but it works on my machine” problem. Meanwhile, enterprises can use Docker to build Agile software delivery pipelines to ship new features faster and more securely.</p>
<p>With its built-in containerization system, Docker is an excellent tool for cloud computing. In turn, Docker Swarm advances clusterization and decentralized design. Sounds too good to be true, right? Well, there are still several cases when not to use Docker. Here are seven of them.</p>
<p><img src="https://images.ctfassets.net/6xhdtf1foerq/2JJ68pTAT6ZhVo05jVwbWI/d79861dd9b194ee83a313ce18f8b624e/Infographics__3_-min.png?fm=png&amp;q=85&amp;w=1000" alt="do not use docker" width="1000" height="1000" loading="lazy"></p>
<p>Let's go through these one by one.</p>
<h2 id="heading-do-not-use-docker-if-you-need-to-boost-speed"><strong>Do Not Use Docker if You Need to Boost Speed</strong></h2>
<p>Docker containers are smaller and require fewer resources than a virtual machine with a server and a database. At the same time, Docker will use as much system resources as the host’s kernel scheduler will allow. You should not expect Docker to speed up an application in any way.</p>
<p>What is more, Docker might even make it slower. If you are working with it, you should set limits on how much memory, CPU, or block IO the container can use. Otherwise, if the kernel detects that the host machine’s memory is running too low to perform important system functions, it could start killing important processes. If the wrong process is killed (including the Docker itself), the system will be unstable.</p>
<p>Unfortunately, Docker’s memory adjustments – the out-of-memory priority on the Docker daemon – do not solve this issue. By contrast, an additional layer between an application and the operating system could also result in speed reduction. Yet, this decrease will be insignificant. Docker containers are not fully isolated and do not contain a complete operating system like any virtual machine.</p>
<h2 id="heading-do-not-use-docker-if-you-prioritize-security"><strong>Do Not Use Docker if You Prioritize Security</strong></h2>
<p>The greatest Docker security advantage is that it breaks the app into smaller parts. If the security of one part is compromised, the rest of them will not be affected.</p>
<p>However, while isolated processes in containers promise improved security, all containers share access to a single host operating system. You risk running Docker containers with incomplete isolation. Any malicious code can get access to your computer memory.</p>
<p>There is a popular practice to run a lot of containers in a single environment. This is how you make your app predisposed to the Resource Abuse type of attacks unless you limit the resource container capabilities. For maximum efficiency and isolation, each container should address one specific area of concern.</p>
<p>Another issue is Docker’s default configuration – users are not namespaced. Namespaces let software resources use other resources only if they belong to a specific namespace.</p>
<p>Running applications with Docker implies running the Docker daemon with root privileges. Any processes that break out of Docker container will have the same privileges on the host as it did in the container. Running your processes inside the containers as a non-privileged user cannot guarantee security. It depends on the capabilities you add or remove. To mitigate the risks of Docker container breakout, you should not download ready-to-use containers from untrusted sources.</p>
<h2 id="heading-do-not-use-docker-if-you-develop-a-desktop-gui-application"><strong>Do Not Use Docker if You Develop a Desktop GUI Application</strong></h2>
<p>Docker does not suit applications that require rich UI. Docker is mainly intended for isolated containers with console-based applications. GUI-based applications are not a priority, their support will rely on the specific case and application. Windows containers are based on either Nano or Core Server – it does not allow users to start up a GUI-based interface or a Docker RDP server in the Docker container.</p>
<p>Yet, you can still <a target="_blank" href="https://hub.docker.com/r/tzutalin/py2qt4/">run GUI-based applications</a> developed with Python and the QT framework in a Linux container. Also, you can use X11 forwarding, but this solution is somewhat awkward.</p>
<h2 id="heading-do-not-use-docker-if-you-want-to-light-up-development-and-debugging"><strong>Do Not Use Docker if You Want to Light Up Development and Debugging</strong></h2>
<p>Docker was created by developers and for developers. It provides environment stability: a container on the development machine will work exactly the same on staging, production, or any other environment. This eliminates the problem of various program versioning in different environments.</p>
<p>With Docker’s help, you can easily add a new dependency to your application. No developer on your team will need to repeat this manipulation on their machine. Everything will be up and running in the container and distributed to the entire team.</p>
<p>At the same time, you have to do some extra setup to code your app in Docker. Moreover, with Docker debugging, you have to configure logs output and set up debugging ports. You may also need to map ports for your applications and services in containers. So, if you have a complicated and tedious deployment process, Docker will help you out a lot. If you have a simple app, it just adds unnecessary complexity.</p>
<h2 id="heading-do-not-use-docker-if-you-need-to-use-different-operating-systems-or-kernels"><strong>Do Not Use Docker if You Need to Use Different Operating Systems or Kernels</strong></h2>
<p>With virtual machines, the hypervisor can abstract an entire device. You can use Microsoft Azure to run both instances of Windows Server and Linux Server at the same time. Docker image, however, requires the same operating system it was created for.</p>
<p>There is a large database of Docker container images –  Docker Hub. Yet, if an image was created on Linux Ubuntu, it will run only on the exact same Ubuntu.</p>
<p>If an app is developed on Windows, but the production runs on Linux, you will not be able to use Docker effectively. Sometimes, it is easier to set up a server if you have several static apps.</p>
<h2 id="heading-do-not-use-docker-if-you-have-a-lot-of-valuable-data-to-store"><strong>Do Not Use Docker if You Have a Lot of Valuable Data to Store</strong></h2>
<p>By design, all Docker files are created inside a container and stored on a writable container layer. It may be difficult to retrieve the data out of the container if a different process needs it. Also, the writable layer of a container is connected to the host machine which the container is running on. If you need to move the data elsewhere, you cannot do it easily. More than that, all the data stored inside a container will be lost forever once the container shuts down.</p>
<p>You have to think of ways to save your data somewhere else first. To keep data safe in Docker, you need to employ an additional tool – Docker Data Volumes. Yet, this solution is still quite clumsy and needs to be improved.</p>
<h2 id="heading-do-not-use-docker-if-you-are-looking-for-the-easiest-technology-to-manage"><strong>Do Not Use Docker if You Are Looking for The Easiest Technology to Manage</strong></h2>
<p>Being introduced in 2012, Docker is still a new technology. As a developer, you might have to update Docker versions regularly. Unfortunately, backward compatibility is not guaranteed. Moreover, the documentation is falling behind the advancement of the technology. As a developer, you will have to figure some things out yourself.</p>
<p>In addition, the monitoring options that Docker offers are quite poor. You can get a quick insight into some simple statistics. Yet, if you want to see some advanced monitoring features, Docker has nothing to offer.</p>
<p>Also, in the case of a large and complex application, the implementation of Docker comes at a cost. Building and maintaining communication between numerous containers on numerous servers will take a lot of time and effort. Yet, there is a helpful tool, which makes it easier to work with multi-container Docker apps, – Docker Compose. Docker Compose defines services, networks, and volumes in a single YAML file.</p>
<p>Nonetheless, the Docker ecosystem is quite fractured – not all the supporting container products work well with one another. Each product is backed by a certain company or community. The heated competition between those results in product incompatibility.</p>
<h2 id="heading-to-wrap-up"><strong>To Wrap Up</strong></h2>
<p>KeenEthics professionals enjoy working with Docker and often use it for app development. Despite some drawbacks, you can easily use it to run and manage apps side by side in isolated containers. </p>
<p>Installing an app can be as simple as running a single command – . Docker also provides a clean and original isolation environment for each test, making it an important and useful tool for automation testing. </p>
<p>Docker features offer benefits in terms of dependency management and security. Augmented with such useful tools as Docker Hub, Docker Swarm, and Docker Compose, Docker is a popular and user-friendly solution.</p>
<p>Despite all the benefits of Docker, you should not use it to containerize each and every application you develop.</p>
<p><em>Remember: Docker is a game-changer. But it is not a one-size-fits-all solution.</em></p>
<p>Docker is not the only such a tool in the market either. The alternatives of Docker are <a target="_blank" href="https://coreos.com/rkt/">rkt</a>, pronounced as ‘rocket’, <a target="_blank" href="https://linuxcontainers.org/">Linux Containers</a>, or <a target="_blank" href="https://openvz.org/">OpenVZ</a>. Each of these with its advantages and disadvantages is quite similar to Docker. The growing popularity and use rates of Docker are caused only by the decision of businesses to adopt it.</p>
<p>Before jumping to conclusions as for should you use Docker or not, research the project requirements. Talk to your teammates or peers and let them help you decide when to use Docker, when not to use containers, and whether it is one of those Docker use cases.</p>
<p>Whether you like it or not, this technology has a future. There are some developers and development agencies that hate Docker and try to eliminate it from all their ongoing projects. At the same time, there are specialists who containerize everything they can because they see Docker as a panacea. Perhaps, you should not join either camp. Stay impartial, stay objective, and make a decision depending on a particular situation.</p>
<h2 id="heading-do-you-have-an-idea-for-a-docker-project">Do you have an idea for a Docker project?</h2>
<p>My company KeenEthics is a team of experienced <a target="_blank" href="https://keenethics.com/services-web-development">web application developers</a>. In case you need a free estimate of a similar project, feel free to <a target="_blank" href="https://keenethics.com/contacts?activeForm=estimate">get in touch</a><em>.</em></p>
<p>You can read more of similar articles on my Keen Blog. Allow me to suggest you read <a target="_blank" href="https://keenethics.com/blog/1554572000000-refactoring">Why to Refactor Your Code?</a> or <a target="_blank" href="https://keenethics.com/blog/outsourcing-vs-outstaffing">Software Development Models Explained:  Outsourcing vs Outstaffing,  Fixed Price vs Time &amp; Material?</a></p>
<h2 id="heading-ps">P.S.</h2>
<p>Also, I would like to say "thank you" to <a target="_blank" href="https://www.linkedin.com/in/oleksiy-pletnov-212b3764/">Alex Pletnov</a> for coauthoring this article as well as the readers for making it to the end!</p>
<p>The original article posted on KeenEthics blog can be found here: <a target="_blank" href="https://keenethics.com/blog/1517306255770-docker-5-cases-when-you-should-not-use-it">7 Cases When Not to Use Docker</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Building Python Data Science Container using Docker ]]>
                </title>
                <description>
                    <![CDATA[ By Faizan Bashir Photo by Unsplash TL;DR Artificial Intelligence(AI) and Machine Learning(ML) are literally on fire these days. Powering a wide spectrum of use-cases ranging from self-driving cars to drug discovery and to God knows what. AI and ML h... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/building-python-data-science-container-using-docker/</link>
                <guid isPermaLink="false">66d45ee4aad1510d0766b617</guid>
                
                    <category>
                        <![CDATA[ Data Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 05 Jul 2019 19:00:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/1_oYWC2Wnc4Nf_mH0WL3ep_w.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Faizan Bashir</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*oYWC2Wnc4Nf_mH0WL3ep_w.jpeg" alt="Image" width="2600" height="1736" loading="lazy"></p>
<p><em>Photo by</em> <a target="_blank" href="https://unsplash.com/@bryangoffphoto"><em>Unsplash</em></a></p>
<h3 id="heading-tldr">TL;DR</h3>
<p>Artificial Intelligence(AI) and Machine Learning(ML) are literally on fire these days. Powering a wide spectrum of use-cases ranging from self-driving cars to drug discovery and to God knows what. AI and ML have a bright and thriving future ahead of them.</p>
<p>On the other hand, Docker revolutionized the computing world through the introduction of ephemeral lightweight containers. Containers basically package all the software required to run inside an image(a bunch of read-only layers) with a COW(Copy On Write) layer to persist the data.</p>
<p>Enough talk let’s get started with building a Python data science container.</p>
<hr>
<h3 id="heading-python-data-science-packages">Python Data Science Packages</h3>
<p>Our Python data science container makes use of the following super cool python packages:</p>
<ol>
<li><p><strong>NumPy</strong>: NumPy or Numeric Python supports large, multi-dimensional arrays and matrices. It provides fast precompiled functions for mathematical and numerical routines. In addition, NumPy optimizes Python programming with powerful data structures for efficient computation of multi-dimensional arrays and matrices.</p>
</li>
<li><p><strong>SciPy</strong>: SciPy provides useful functions for regression, minimization, Fourier-transformation, and many more. Based on NumPy, SciPy extends its capabilities. SciPy’s main data structure is again a multidimensional array, implemented by Numpy. The package contains tools that help with solving linear algebra, probability theory, integral calculus, and many more tasks.</p>
</li>
<li><p><strong>Pandas</strong>: Pandas offer versatile and powerful tools for manipulating data structures and performing extensive data analysis. It works well with incomplete, unstructured, and unordered real-world data — and comes with tools for shaping, aggregating, analyzing, and visualizing datasets.</p>
</li>
<li><p><strong>SciKit-Learn</strong>: Scikit-learn is a Python module integrating a wide range of state-of-the-art machine learning algorithms for medium-scale supervised and unsupervised problems. It is one of the best-known machine-learning libraries for python. The Scikit-learn package focuses on bringing machine learning to non-specialists using a general-purpose high-level language. The primary emphasis is upon ease of use, performance, documentation, and API consistency. With minimal dependencies and easy distribution under the simplified BSD license, SciKit-Learn is widely used in academic and commercial settings. Scikit-learn exposes a concise and consistent interface to the common machine learning algorithms, making it simple to bring ML into production systems.</p>
</li>
<li><p><strong>Matplotlib</strong>: Matplotlib is a Python 2D plotting library, capable of producing publication quality figures in a wide variety of hardcopy formats and interactive environments across platforms. Matplotlib can be used in Python scripts, the Python and IPython shell, the Jupyter notebook, web application servers, and four graphical user interface toolkits.</p>
</li>
<li><p><strong>NLTK</strong>: NLTK is a leading platform for building Python programs to work with human language data. It provides easy-to-use interfaces to over 50 corpora and lexical resources such as WordNet, along with a suite of text processing libraries for classification, tokenization, stemming, tagging, parsing, and semantic reasoning.</p>
</li>
</ol>
<hr>
<h3 id="heading-building-the-data-science-container">Building the Data Science Container</h3>
<p>Python is fast becoming the go-to language for data scientists and for this reason we are going to use Python as the language of choice for building our data science container.</p>
<h4 id="heading-the-base-alpine-linux-image">The Base Alpine Linux Image</h4>
<p>Alpine Linux is a tiny Linux distribution designed for power users who appreciate security, simplicity and resource efficiency.</p>
<p>As claimed by <a target="_blank" href="https://alpinelinux.org/">Alpine</a>:</p>
<blockquote>
<p><em>Small. Simple. Secure. Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.</em></p>
</blockquote>
<p>The Alpine image is surprisingly tiny with a size of no more than 8MB for containers. With minimal packages installed to reduce the attack surface on the underlying container. This makes Alpine an image of choice for our data science container.</p>
<p>Downloading and Running an Alpine Linux container is as simple as:</p>
<pre><code class="lang-bash">$ docker container run --rm alpine:latest cat /etc/os-release
</code></pre>
<p>In our, Dockerfile we can simply use the Alpine base image as:</p>
<pre><code class="lang-bash">FROM alpine:latest
</code></pre>
<hr>
<h4 id="heading-talk-is-cheap-lets-build-the-dockerfile">Talk is cheap let’s build the Dockerfile</h4>
<p>Now let’s work our way through the Dockerfile.</p>
<pre><code class="lang-bash">FROM alpine:latest

LABEL MAINTAINER=<span class="hljs-string">"Faizan Bashir &lt;faizan.ibn.bashir@gmail.com&gt;"</span>

<span class="hljs-comment"># Linking of locale.h as xlocale.h</span>
<span class="hljs-comment"># This is done to ensure successfull install of python numpy package</span>
<span class="hljs-comment"># see https://forum.alpinelinux.org/comment/690#comment-690 for more information.</span>

WORKDIR /var/www/

<span class="hljs-comment"># SOFTWARE PACKAGES</span>
<span class="hljs-comment">#   * musl: standard C library</span>
<span class="hljs-comment">#   * lib6-compat: compatibility libraries for glibc</span>
<span class="hljs-comment">#   * linux-headers: commonly needed, and an unusual package name from Alpine.</span>
<span class="hljs-comment">#   * build-base: used so we include the basic development packages (gcc)</span>
<span class="hljs-comment">#   * bash: so we can access /bin/bash</span>
<span class="hljs-comment">#   * git: to ease up clones of repos</span>
<span class="hljs-comment">#   * ca-certificates: for SSL verification during Pip and easy_install</span>
<span class="hljs-comment">#   * freetype: library used to render text onto bitmaps, and provides support font-related operations</span>
<span class="hljs-comment">#   * libgfortran: contains a Fortran shared library, needed to run Fortran</span>
<span class="hljs-comment">#   * libgcc: contains shared code that would be inefficient to duplicate every time as well as auxiliary helper routines and runtime support</span>
<span class="hljs-comment">#   * libstdc++: The GNU Standard C++ Library. This package contains an additional runtime library for C++ programs built with the GNU compiler</span>
<span class="hljs-comment">#   * openblas: open source implementation of the BLAS(Basic Linear Algebra Subprograms) API with many hand-crafted optimizations for specific processor types</span>
<span class="hljs-comment">#   * tcl: scripting language</span>
<span class="hljs-comment">#   * tk: GUI toolkit for the Tcl scripting language</span>
<span class="hljs-comment">#   * libssl1.0: SSL shared libraries</span>
ENV PACKAGES=<span class="hljs-string">"\
    dumb-init \
    musl \
    libc6-compat \
    linux-headers \
    build-base \
    bash \
    git \
    ca-certificates \
    freetype \
    libgfortran \
    libgcc \
    libstdc++ \
    openblas \
    tcl \
    tk \
    libssl1.0 \
"</span>

<span class="hljs-comment"># PYTHON DATA SCIENCE PACKAGES</span>
<span class="hljs-comment">#   * numpy: support for large, multi-dimensional arrays and matrices</span>
<span class="hljs-comment">#   * matplotlib: plotting library for Python and its numerical mathematics extension NumPy.</span>
<span class="hljs-comment">#   * scipy: library used for scientific computing and technical computing</span>
<span class="hljs-comment">#   * scikit-learn: machine learning library integrates with NumPy and SciPy</span>
<span class="hljs-comment">#   * pandas: library providing high-performance, easy-to-use data structures and data analysis tools</span>
<span class="hljs-comment">#   * nltk: suite of libraries and programs for symbolic and statistical natural language processing for English</span>
ENV PYTHON_PACKAGES=<span class="hljs-string">"\
    numpy \
    matplotlib \
    scipy \
    scikit-learn \
    pandas \
    nltk \
"</span> 

RUN apk add --no-cache --virtual build-dependencies python --update py-pip \
    &amp;&amp; apk add --virtual build-runtime \
    build-base python-dev openblas-dev freetype-dev pkgconfig gfortran \
    &amp;&amp; ln -s /usr/include/locale.h /usr/include/xlocale.h \
    &amp;&amp; pip install --upgrade pip \
    &amp;&amp; pip install --no-cache-dir <span class="hljs-variable">$PYTHON_PACKAGES</span> \
    &amp;&amp; apk del build-runtime \
    &amp;&amp; apk add --no-cache --virtual build-dependencies <span class="hljs-variable">$PACKAGES</span> \
    &amp;&amp; rm -rf /var/cache/apk/*

CMD [<span class="hljs-string">"python"</span>]
</code></pre>
<p>The <code>FROM</code> directive is used to set <code>alpine:latest</code> as the base image. Using the <code>WORKDIR</code> directive we set the <code>/var/www</code> as the working directory for our container. The <code>ENV PACKAGES</code> lists the software packages required for our container like <code>git</code>, <code>blas</code> and <code>libgfortran</code>. The python packages for our data science container are defined in the <code>ENV PACKAGES</code>.</p>
<p>We have combined all the commands under a single Dockerfile <code>RUN</code> directive to reduce the number of layers which in turn helps in reducing the resultant image size.</p>
<hr>
<h4 id="heading-building-and-tagging-the-image">Building and tagging the image</h4>
<p>Now that we have our Dockerfile defined, navigate to the folder with the Dockerfile using the terminal and build the image using the following command:</p>
<pre><code class="lang-bash">$ docker build -t faizanbashir/python-datascience:2.7 -f Dockerfile .
</code></pre>
<p>The <code>-t</code> flag is used to name a tag in the 'name:tag' format. The <code>-f</code> tag is used to define the name of the Dockerfile (Default is 'PATH/Dockerfile').</p>
<hr>
<h4 id="heading-running-the-container">Running the container</h4>
<p>We have successfully built and tagged the docker image, now we can run the container using the following command:</p>
<pre><code class="lang-bash">$ docker container run --rm -it faizanbashir/python-datascience:2.7 python
</code></pre>
<p>Voila, we are greeted by the sight of a python shell ready to perform all kinds of cool data science stuff.</p>
<pre><code class="lang-bash">Python 2.7.15 (default, Aug 16 2018, 14:17:09) [GCC 6.4.0] on linux2 Type <span class="hljs-string">"help"</span>, <span class="hljs-string">"copyright"</span>, <span class="hljs-string">"credits"</span> or <span class="hljs-string">"license"</span> <span class="hljs-keyword">for</span> more information. &gt;&gt;&gt;
</code></pre>
<p>Our container comes with Python 2.7, but don’t be sad if you wanna work with Python 3.6. Lo, behold the Dockerfile for Python 3.6:</p>
<pre><code class="lang-bash">https://gist.github.com/faizanbashir/9443a7149cc53f81d84d0d356f871ec7<span class="hljs-comment">#file-datascience-python3-6-dockerfile</span>
</code></pre>
<p>Build and tag the image like so:</p>
<pre><code class="lang-bash">FROM alpine:latest

LABEL MAINTAINER=<span class="hljs-string">"Faizan Bashir &lt;faizan.ibn.bashir@gmail.com&gt;"</span>

<span class="hljs-comment"># Linking of locale.h as xlocale.h</span>
<span class="hljs-comment"># This is done to ensure successfull install of python numpy package</span>
<span class="hljs-comment"># see https://forum.alpinelinux.org/comment/690#comment-690 for more information.</span>

WORKDIR /var/www/

<span class="hljs-comment"># SOFTWARE PACKAGES</span>
<span class="hljs-comment">#   * musl: standard C library</span>
<span class="hljs-comment">#   * lib6-compat: compatibility libraries for glibc</span>
<span class="hljs-comment">#   * linux-headers: commonly needed, and an unusual package name from Alpine.</span>
<span class="hljs-comment">#   * build-base: used so we include the basic development packages (gcc)</span>
<span class="hljs-comment">#   * bash: so we can access /bin/bash</span>
<span class="hljs-comment">#   * git: to ease up clones of repos</span>
<span class="hljs-comment">#   * ca-certificates: for SSL verification during Pip and easy_install</span>
<span class="hljs-comment">#   * freetype: library used to render text onto bitmaps, and provides support font-related operations</span>
<span class="hljs-comment">#   * libgfortran: contains a Fortran shared library, needed to run Fortran</span>
<span class="hljs-comment">#   * libgcc: contains shared code that would be inefficient to duplicate every time as well as auxiliary helper routines and runtime support</span>
<span class="hljs-comment">#   * libstdc++: The GNU Standard C++ Library. This package contains an additional runtime library for C++ programs built with the GNU compiler</span>
<span class="hljs-comment">#   * openblas: open source implementation of the BLAS(Basic Linear Algebra Subprograms) API with many hand-crafted optimizations for specific processor types</span>
<span class="hljs-comment">#   * tcl: scripting language</span>
<span class="hljs-comment">#   * tk: GUI toolkit for the Tcl scripting language</span>
<span class="hljs-comment">#   * libssl1.0: SSL shared libraries</span>
ENV PACKAGES=<span class="hljs-string">"\
    dumb-init \
    musl \
    libc6-compat \
    linux-headers \
    build-base \
    bash \
    git \
    ca-certificates \
    freetype \
    libgfortran \
    libgcc \
    libstdc++ \
    openblas \
    tcl \
    tk \
    libssl1.0 \
    "</span>

<span class="hljs-comment"># PYTHON DATA SCIENCE PACKAGES</span>
<span class="hljs-comment">#   * numpy: support for large, multi-dimensional arrays and matrices</span>
<span class="hljs-comment">#   * matplotlib: plotting library for Python and its numerical mathematics extension NumPy.</span>
<span class="hljs-comment">#   * scipy: library used for scientific computing and technical computing</span>
<span class="hljs-comment">#   * scikit-learn: machine learning library integrates with NumPy and SciPy</span>
<span class="hljs-comment">#   * pandas: library providing high-performance, easy-to-use data structures and data analysis tools</span>
<span class="hljs-comment">#   * nltk: suite of libraries and programs for symbolic and statistical natural language processing for English</span>
ENV PYTHON_PACKAGES=<span class="hljs-string">"\
    numpy \
    matplotlib \
    scipy \
    scikit-learn \
    pandas \
    nltk \
    "</span> 

RUN apk add --no-cache --virtual build-dependencies python3 \
    &amp;&amp; apk add --virtual build-runtime \
    build-base python3-dev openblas-dev freetype-dev pkgconfig gfortran \
    &amp;&amp; ln -s /usr/include/locale.h /usr/include/xlocale.h \
    &amp;&amp; python3 -m ensurepip \
    &amp;&amp; rm -r /usr/lib/python*/ensurepip \
    &amp;&amp; pip3 install --upgrade pip setuptools \
    &amp;&amp; ln -sf /usr/bin/python3 /usr/bin/python \
    &amp;&amp; ln -sf pip3 /usr/bin/pip \
    &amp;&amp; rm -r /root/.cache \
    &amp;&amp; pip install --no-cache-dir <span class="hljs-variable">$PYTHON_PACKAGES</span> \
    &amp;&amp; apk del build-runtime \
    &amp;&amp; apk add --no-cache --virtual build-dependencies <span class="hljs-variable">$PACKAGES</span> \
    &amp;&amp; rm -rf /var/cache/apk/*

CMD [<span class="hljs-string">"python3"</span>]
</code></pre>
<p>Run the container like so:</p>
<pre><code class="lang-bash">$ docker container run --rm -it faizanbashir/python-datascience:3.6 python
</code></pre>
<p>With this, you have a ready to use container for doing all kinds of cool data science stuff.</p>
<hr>
<h3 id="heading-serving-puddin">Serving Puddin’</h3>
<p>Figures, you have the time and resources to set up all this stuff. In case you don’t, you can pull the existing images that I have already built and pushed to Docker’s registry <a target="_blank" href="https://hub.docker.com/">Docker Hub</a> using:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># For Python 2.7 pull$ docker pull faizanbashir/python-datascience:2.7</span>
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># For Python 3.6 pull$ docker pull faizanbashir/python-datascience:3.6</span>
</code></pre>
<p>After pulling the images you can use the image or extend the same in your Dockerfile file or use it as an image in your docker-compose or stack file.</p>
<hr>
<h3 id="heading-aftermath">Aftermath</h3>
<p>The world of AI, ML is getting pretty exciting these days and will continue to become even more exciting. Big players are investing heavily in these domains. About time you start to harness the power of data, who knows it might lead to something wonderful.</p>
<p>You can check out the code here.</p>
<p><a target="_blank" href="https://github.com/faizanbashir/python-datascience"><strong>faizanbashir/python-datascience</strong></a><br><a target="_blank" href="https://github.com/faizanbashir/python-datascience">_Docker image for python datascience container with NumPy, SciPy, Scikit-learn, Matplotlib, nltk, pandas packages…_github.com</a></p>
<p>I hope this article helped in building containers for your data science projects. Clap if it increased your knowledge, help it reach more people.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Practical Introduction to Docker Compose ]]>
                </title>
                <description>
                    <![CDATA[ By Faizan Bashir Docker containers opened a world of possibilities for the tech community, hassles in setting up new software were decreased unlike old times when a mess was to be sorted by a grievous format, it reduced the time to set up and use new... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-practical-introduction-to-docker-compose/</link>
                <guid isPermaLink="false">66d45edd182810487e0ce184</guid>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 05 Jul 2019 18:15:08 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/1_JK4VDnsrF6YnAb2nyhMsdQ-2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Faizan Bashir</p>
<p>Docker containers opened a world of possibilities for the tech community, hassles in setting up new software were decreased unlike old times when a mess was to be sorted by a grievous format, it reduced the time to set up and use new software which eventually played a big part for techies to learn new things, roll it out in a container and scrap it when done. Things became easy, and the best thing its open source anyone and everyone can use it, comes with a little learning curve though.</p>
<p>Out of the myriad possibilities was the possibility of implementing complex technology stacks for our applications, which previously would have been the domain of experts. Today with the help of containers software engineers with sound understanding of the underlying systems can implement a complex stack and why not it’s the need of the hour, the figure of speech “Jack of all trades” got a fancy upgrade; “Master of some” based on the needs of the age. Simply put “T” shaped skills.</p>
<p>The possibility of defining a complex stack in a file and running it with a single command, pretty tempting huh. The guys at Docker Inc. choose to call it Docker compose.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*1g8v7eeFV2OWt1Tkmoc-4A.jpeg" alt="Image" width="750" height="422" loading="lazy"></p>
<p>In this article, we will use Docker’s example Voting App and deploy it using Docker compose.</p>
<hr>
<h3 id="heading-docker-compose">Docker Compose</h3>
<p>In the words of Docker Inc.</p>
<blockquote>
<p><em>Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.</em></p>
</blockquote>
<hr>
<h3 id="heading-the-voting-app">The Voting App</h3>
<p>Introducing the most favourite demonstration app for the Docker community “The Voting App”, as if it needs an introduction at all. This is a simple application based on micro-services architecture, consisting of 5 simple services.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*DIZdPFJO4EQbPNq0pR_b8g.png" alt="Image" width="1024" height="768" loading="lazy"></p>
<p><em>Voting app architecture:</em> <a target="_blank" href="https://github.com/docker/example-voting-app"><em>https://github.com/docker/example-voting-app</em></a></p>
<ol>
<li><p><strong>Voting-App</strong>: Frontend of the application written in Python, used by users to cast their votes.</p>
</li>
<li><p><strong>Redis</strong>: In-memory database, used as intermediate storage.</p>
</li>
<li><p><strong>Worker</strong>: .Net service, used to fetch votes from Redis and store in Postres database.</p>
</li>
<li><p><strong>DB</strong>: PostgreSQL database, used as database.</p>
</li>
<li><p><strong>Result-App</strong>: Frontend of the application written in Node.js, displays the voting results.</p>
</li>
</ol>
<p>The Voting repo has a file called <code>docker-compose.yml</code> this file contains the configuration for creating the containers, exposing ports, binding volumes and connecting containers through networks required for the voting app to work. Sounds like a lot of pretty long <code>docker run</code> and <code>docker network create</code> commands otherwise, docker compose allows us to put all of that stuff in a single docker-compose file in <a target="_blank" href="http://yaml.org/start.html">yaml</a> format.</p>
<pre><code class="lang-bash">version: <span class="hljs-string">"3"</span>

services:
  vote:
    build: ./vote
    <span class="hljs-built_in">command</span>: python app.py
    volumes:
     - ./vote:/app
    ports:
      - <span class="hljs-string">"5000:80"</span>
    networks:
      - front-tier
      - back-tier

  result:
    build: ./result
    <span class="hljs-built_in">command</span>: nodemon server.js
    volumes:
      - ./result:/app
    ports:
      - <span class="hljs-string">"5001:80"</span>
      - <span class="hljs-string">"5858:5858"</span>
    networks:
      - front-tier
      - back-tier

  worker:
    build:
      context: ./worker
    depends_on:
      - <span class="hljs-string">"redis"</span>
    networks:
      - back-tier

  redis:
    image: redis:alpine
    container_name: redis
    ports: [<span class="hljs-string">"6379"</span>]
    networks:
      - back-tier

  db:
    image: postgres:9.4
    container_name: db
    volumes:
      - <span class="hljs-string">"db-data:/var/lib/postgresql/data"</span>
    networks:
      - back-tier

volumes:
  db-data:

networks:
  front-tier:
  back-tier:
</code></pre>
<p>Git <code>clone</code> and <code>cd</code> into the voting app repo.</p>
<p><a target="_blank" href="https://github.com/dockersamples/example-voting-app"><strong>dockersamples/example-voting-app</strong></a><br><a target="_blank" href="https://github.com/dockersamples/example-voting-app">_example-voting-app - Example Docker Compose app_github.com</a></p>
<hr>
<h3 id="heading-compose-time">Compose Time</h3>
<p>With all of our application defined in a single compose file we can take a sigh of relief, chill and simply run the application. The beauty of compose lies in the fact that a single command creates all the services, wires up the networks(literally), mounts all volumes and exposes the ports. Its time to welcome the <code>up</code> command, its performs all of the aforementioned tasks.$ docker-compose up</p>
<p>After lots of “Pull complete”, hundreds of megabytes and few minutes (maybe more). . .</p>
<p>Voila, we have the voting app up and running.</p>
<p>Command <code>docker ps</code> lists all the running containers</p>
<pre><code class="lang-bash">$ docker ps -a --format=<span class="hljs-string">"table {{.Names}}\t{{.Image}}\t{{.Ports}}"</span> 
NAMES               IMAGE               PORTS
voting_worker_1     voting_worker      
db                  postgres:9.4        5432/tcp
voting_vote_1       voting_vote         0.0.0.0:5000-&gt;80/tcp
voting_result_1     voting_result       0.0.0.0:5858-&gt;5858/tcp, 0.0.0.0:5001-&gt;80/tcp
redis               redis:alpine        0.0.0.0:32768-&gt;6379/tcp
</code></pre>
<p>The above command displays all the running containers, respective images and the exposed port numbers.</p>
<p>The Voting app can be accessed on <a target="_blank" href="http://localhost:5000/">http://localhost:5000</a></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*2OBAYVFG35tX6dHI08TWPg.png" alt="Image" width="1365" height="648" loading="lazy"></p>
<p>Likewise the Voting results app can be accessed on <a target="_blank" href="http://localhost:5001/">http://localhost:5001</a></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*E-WleHhSji49ZLIafS8xgQ.png" alt="Image" width="1365" height="648" loading="lazy"></p>
<p>Each vote cast on the Voting app is first stored in the Redis in-memory database, the .Net worker service fetches the vote and stores it in the Postgres DB which is accessed by the Node.js frontend.</p>
<hr>
<h3 id="heading-compose-features">Compose Features</h3>
<p>Compose provide the flexibility to use a project name to isolate the environments from each other, the project name is the base name of the directory that contains the project. In our voting app this is signified by the name of the containers <code>voting_worker_1</code> where <code>voting</code> is the base name of the directory. We can set a custom project name using the <code>-p</code> flag followed by the custom name.</p>
<p>Compose preserves all volumes used by the services defined in the compose file, thus no data is lost when the containers are recreated using <code>docker-compose up</code>. Another cool feature is that only the containers which have changed are recreated, the containers whose state did not change remain untouched.</p>
<p>Another cool feature is the support for variables in the compose file, we can define variables in a <code>.env</code> file and use them in the docker-compose file. Here the <code>POSTGRES_VERSION=9.4</code> can defined in the environment file or can be defined in the shell. It is used in the compose file in the following manner:</p>
<pre><code class="lang-bash">db:  
  image: <span class="hljs-string">"postgres:<span class="hljs-variable">${POSTGRES_VERSION}</span>"</span>
</code></pre>
<hr>
<h3 id="heading-command-cheat-sheet">Command Cheat Sheet</h3>
<p>It's easy as breeze to start, stop and play around with compose.</p>
<pre><code class="lang-bash">$ docker-compose up -d
$ docker-compose down
$ docker-compose start
$ docker-compose stop
$ docker-compose build
$ docker-compose logs -f db
$ docker-compose scale db=4
$ docker-compose events
$ docker-compose <span class="hljs-built_in">exec</span> db bash
</code></pre>
<hr>
<h3 id="heading-summary">Summary</h3>
<p>Docker Compose is a great tool to quickly deploy and scrap containers, the compose file can run seamlessly on any machine installed with docker-compose. Experimentation and learning technologies is just a Compose file away ;).</p>
<p>I hope this article helped in the understanding of Docker Compose. I’d love to hear about how you use Docker Compose in your projects. Share the knowledge, help it reach more people.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Docker Data Containers ]]>
                </title>
                <description>
                    <![CDATA[ By Faizan Bashir There is more than one way to manage data in Docker container. Say hello to the Data Containers. Simply put data containers are containers whose job is just to store/manage data. Similar to other containers they are managed by the ho... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-data-containers/</link>
                <guid isPermaLink="false">66d45ef09208fb118cc6cf9b</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 05 Jul 2019 18:05:19 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/1_AUiK5PwnsPG_xaT9jcVoSA-2.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Faizan Bashir</p>
<p>There is more than one way to manage data in Docker container. Say hello to the Data Containers.</p>
<p>Simply put data containers are containers whose job is just to store/manage data.</p>
<p>Similar to other containers they are managed by the host system. However, they don’t show up when you perform a <code>docker ps</code> command.</p>
<p>To create a Data Container we first create a container with a well-known name for future reference. We use <em>busybox</em> as the base as it’s small and lightweight in case we want to explore and move the container to another host.</p>
<p>When creating the container, we also provide a volume <code>-v</code> option to define where other containers will be reading/writing data.</p>
<pre><code>$ docker create -v /config --name dataContainer busybox
</code></pre><p>With the container in place, we can now copy files from our local client directory into the container.</p>
<p>To copy files into a container you use the command <code>docker cp</code>. The following command will copy the <em>config.conf</em> file into the <em>config</em> directory of <em>dataContainer</em>.</p>
<pre><code>$ docker cp config.conf dataContainer:<span class="hljs-regexp">/config/</span>
</code></pre><p>Now our Data Container has our config, we can reference the container when we launch dependent containers requiring the configuration file.</p>
<p>Using the magical <code>--volumes-from &lt;container&gt;</code> option we can use the mount volumes from other containers inside the container being launched. In this case, we’ll launch an Ubuntu container which has reference to our Data Container. When we list the config directory, it will show the files from the attached container.</p>
<pre><code>$ docker run --volumes-<span class="hljs-keyword">from</span> dataContainer ubuntu ls/config
</code></pre><p>If a <em>/config</em> directory already existed then, the volumes-from would override and be the directory used. You can map multiple volumes to a container.</p>
<hr>
<h3 id="heading-import-and-export-container-data"><strong>Import and Export Container data</strong></h3>
<p>Data can be imported and exported from a container, using the <code>docker export</code> command.</p>
<p>We can move the Data Container to another machine simply by exporting it to a .tar file.</p>
<pre><code>$ docker <span class="hljs-keyword">export</span> dataContainer &gt; dataContainer.tar
</code></pre><p>Likewise we can import the Data Container back into Docker.</p>
<pre><code>$ docker <span class="hljs-keyword">import</span> dataContainer.tar
</code></pre> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
