{"componentChunkName":"component---src-templates-blog-post-js","path":"/03-docker-creating-custom-docker-images/","result":{"data":{"site":{"siteMetadata":{"title":"Yamenai"}},"markdownRemark":{"id":"0e52cfc4-45a8-5989-803f-628f9ecdb0d2","excerpt":"Let’s take a moment to consider what is important for local development. For me, I want to make sure all my developers are using the same dependencies, and I…","html":"<p>Let’s take a moment to consider what is important for local development. For me, I want to make sure all my developers are using the same dependencies, and I don’t want to worry about what versions they have installed. No more “but it works on my machine” excuses. At the same time, I want to make sure we retain the conveniences of HMR (Hot Module Replacement) so that developers don’t need to constantly refresh the application to see their changes reflected. We don’t want to lose fast feedback.</p>\n<p>In this article, we’ll look at how we can setup Docker for a boilerplate VueJS app with custom <code class=\"language-text\">Dockerfile</code>s from which our images and containers will be built and how we gain efficiencies from these.</p>\n<p>In case you missed the first part in this series, <a href=\"https://benjaminmartin.dev/02-docker-learning-the-command-line/\">check here to learn more about the command line interface</a> that Docker ships with. We need to use the commands from that article in this section. If you are already familiar with Docker CLI, please continue to follow along.</p>\n<h2>Prerequisite: Create our project</h2>\n<p>This is of course a Docker article, so please ensure you have Docker installed. You can follow <a href=\"https://docs.docker.com/install/\">the official install instructions for Docker here</a>. Since I’m using Vue, I’ve used the VueCLI to spin up a quick workspace with <code class=\"language-text\">vue create docker-demo</code>.</p>\n<blockquote>\n<p>The configuration I selected (seen below) will be relevant to do E2E testing and unit testing which will become part of our CI/CD pipeline.</p>\n</blockquote>\n<p><img src=\"/3330dd7c23adee2735f998a228d35902/03-vue-cli.gif\" alt=\"Vue CLI Create Project\" title=\"Vue CLI Create Demo Project\"></p>\n<p>Once everything is installed, <code class=\"language-text\">cd</code> into our new project folder, open an IDE and let’s dig in.</p>\n<h2>Custom Docker Image for Development</h2>\n<p>If you’ve played with Docker but not built your own image, you probably know we specify an image when we execute our <code class=\"language-text\">docker run</code> command. Those images are pulled from Docker Hub or some other remote repository (if that image is not found locally). In our case though, we want to build a custom image.</p>\n<p>In the root of our project, create a file named <code class=\"language-text\">Dockerfile.dev</code>. This will be our development image. In that file, copy the following code into it.</p>\n<div class=\"gatsby-highlight\" data-language=\"dockerfile\"><pre class=\"language-dockerfile\"><code class=\"language-dockerfile\"><span class=\"token comment\"># Base Image</span>\n<span class=\"token keyword\">FROM</span> node<span class=\"token punctuation\">:</span>9.11.1\n\n<span class=\"token keyword\">ENV</span> NODE_ENV=development\n<span class=\"token keyword\">ENV</span> PORT=8080\n\n<span class=\"token keyword\">WORKDIR</span> /usr/src/app\n<span class=\"token keyword\">COPY</span> package*.json /usr/src/app/\n<span class=\"token keyword\">RUN</span> cd /usr/src/app &amp;&amp; CI=true npm install\n\n<span class=\"token keyword\">EXPOSE</span> 8080\n<span class=\"token keyword\">CMD</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"npm\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"run\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"serve\"</span><span class=\"token punctuation\">]</span></code></pre></div>\n<p>Ok… but what does all this do? Let’s dig into it.</p>\n<h3>Dockerfile Commands and Keywords</h3>\n<p><strong><code class=\"language-text\">FROM</code></strong> specifies the preexisting image on which to build our custom image. Since we are running a node application, we’ve chosen one of their official Docker images.</p>\n<blockquote>\n<p>FROM node:9.11.1 means our application image will start with the node v 9.11.1 image</p>\n</blockquote>\n<p><strong><code class=\"language-text\">ENV</code></strong> sets environment variables</p>\n<blockquote>\n<p><strong><code class=\"language-text\">ENV PORT=8080</code></strong> sets the environment variable <code class=\"language-text\">PORT</code> for later use</p>\n<p><strong><code class=\"language-text\">ENV NODE_ENV=development</code></strong> sets the environment variable <code class=\"language-text\">NODE_ENV</code> for use within our app</p>\n</blockquote>\n<p><strong><code class=\"language-text\">WORKDIR</code></strong> sets the working directory within the container</p>\n<blockquote>\n<p><strong><code class=\"language-text\">WORKDIR /usr/src/app</code></strong> defines <code class=\"language-text\">/usr/src/app/</code> as our working directory within the docker image</p>\n</blockquote>\n<p><strong><code class=\"language-text\">COPY</code></strong> copies new files, directories or remote files into the container/image</p>\n<blockquote>\n<p><strong><code class=\"language-text\">COPY package*.json /usr/src/app/</code></strong> copies our <code class=\"language-text\">package.json</code> and <code class=\"language-text\">package-lock.json</code> into our working directory</p>\n</blockquote>\n<p><strong><code class=\"language-text\">RUN</code></strong> executes a command in a new layer on top of the current image and commits it. When you run the build, you will see a hash representing each layer of our final image</p>\n<blockquote>\n<p><strong><code class=\"language-text\">RUN cd /usr/src/app/ &amp;&amp; CI=true npm install</code></strong> changes the working directory to where the <code class=\"language-text\">package.json</code> is and installs all our dependencies to this folder within the image. This makes it so that the image holds frozen copies of the dependencies. Our Docker image, not our host machine, is responsible for our dependencies</p>\n</blockquote>\n<p><strong><code class=\"language-text\">EXPOSE</code></strong> allows us to access a port on the container from our host machine</p>\n<blockquote>\n<p><strong><code class=\"language-text\">EXPOSE 8080</code></strong> matches the port on which our app is running, inside the container and allows us to access our app from our host machine</p>\n</blockquote>\n<p><strong><code class=\"language-text\">CMD</code></strong> provides the default initialization command to run when our container is created, like a startup script</p>\n<blockquote>\n<p><strong><code class=\"language-text\">CMD [&quot;npm&quot;, &quot;run&quot;, &quot;serve&quot;]</code></strong> sets this as our default command when we start our container. This is not run when building the image, it only defines what command should be run when the container starts.</p>\n</blockquote>\n<p>I know you’re anxious to get this running, but hold your horses. Let’s look <em>closer</em> at our <code class=\"language-text\">Dockerfile.dev</code> and understand <em>why</em> we did what we did.</p>\n<h3>Dockerfile Structure Recommendations</h3>\n<p>So, <em>Where’s my app?</em></p>\n<p>Right. We didn’t use the <code class=\"language-text\">COPY</code> command to copy our full workspace. Had we done so, we’d need to run <code class=\"language-text\">docker build</code> and <code class=\"language-text\">docker run</code> for every code change. We don’t want to do this over and over for development. We can be more efficient</p>\n<h4>Caching Dependencies</h4>\n<p>We are taking advantage of how Docker layers the images. As Docker builds our image, you’ll see a hash for each layer as it is completed. What’s more is that Docker also caches these layers. If Docker can see that nothing has changed on that layer from a previous build (and previous layers are also identical) then Docker will use a cached version of that layer, saving you and your developers precious time! When a layer changes, any cached layers on top of it are invalidated and will be rebuilt.</p>\n<p><em>Therefore, if there is no change to our <code class=\"language-text\">package.json</code> or the <code class=\"language-text\">package-lock.json</code> then our entire image is cacheable and doesn’t need to be rebuilt!</em></p>\n<h4>Priority</h4>\n<p>This is also why you want to have other <code class=\"language-text\">Dockerfile</code> commands that change less frequently near the top of our file. As soon as one layer of our cache is invalidated, for example, if you change <code class=\"language-text\">ENV PORT=8080</code> to another port, that cached layer and every cached layer after it is invalidated and Docker will have to rebuild those layers.</p>\n<h3>Building the Custom Docker Image</h3>\n<p>Now, build the image with this command: <code class=\"language-text\">docker build --tag docker_demo:latest --file Dockerfile.dev .</code></p>\n<p><img src=\"/2e8f4f5e155d6a3e47dc36e4f690bdf3/03-docker-build.gif\" alt=\"Docker Build from custom Dockerfile\" title=\"Using Docker to Build a Custom Dockerfile\"></p>\n<blockquote>\n<p>Using <strong><code class=\"language-text\">--tag</code></strong> in the <code class=\"language-text\">docker build</code> command allows us to easily reference this image from our <code class=\"language-text\">docker run</code> command</p>\n<p>The <code class=\"language-text\">.</code> at the end of the <code class=\"language-text\">docker build</code> command references the context where our custom <code class=\"language-text\">Dockerfile</code> can be found. So, this command should be run from the root of our project directory</p>\n</blockquote>\n<p>You can run it with <code class=\"language-text\">docker run docker_demo:latest</code>, but unfortunately, we have more work to do to get it working quickly and easily from the command line.</p>\n<h3>Running our Container: Quality of Life Improvements</h3>\n<p>We’re going to be executing our <code class=\"language-text\">docker run</code> command daily, if not more frequently. However, if we simply execute the <code class=\"language-text\">docker run docker_demo:latest</code> command, Docker will create a <em>new</em> container each time. Docker won’t stop the old container unless you do so explicitly. This is very useful in many cases, but since we’ve hardcoded the host port, we’ll run into port collisions on our host machine.</p>\n<p>In order for us to easily stop and remove our old containers, we should name them so we can easily refer to them later. Additionally, I want the running container to be removed if I cancel the running process.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">docker run --rm -it \\\n--name docker_demo_container \\\ndocker_demo:latest</code></pre></div>\n<p><img src=\"/8a88aa60710bdae9b174db63798b6bf8/03-docker-run.gif\" alt=\"Running the Docker Image we Built\" title=\"Running a Docker container of a Custom Image\"></p>\n<h4>What was added?</h4>\n<p>We added a <code class=\"language-text\">--name</code> field to the end of our run command. This allows us to reference the container without looking up the hash. Now, we can easily stop our container by name.</p>\n<p>We also added the <code class=\"language-text\">--rm</code> and <code class=\"language-text\">-it</code> flags to our <code class=\"language-text\">docker run</code> command. The <code class=\"language-text\">--rm</code> flag tells Docker to remove the container if and when it is stopped. The <code class=\"language-text\">-it</code> flag keeps the terminal live and interactive once the container is started.</p>\n<h4>Mounting Host Directories</h4>\n<p>Let’s go back to our <code class=\"language-text\">docker run</code> command and let’s find a way to mount our workspace directory to a folder within our container. We can do this by adding a mount point to our container in the <code class=\"language-text\">docker run</code> command. This will tell Docker that we want to create an active link between our host machine’s folder (<code class=\"language-text\">src</code>) and the Docker container folder (<code class=\"language-text\">dst</code>). Our new command should look like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">docker run --rm -it \\\n--name docker_demo_container \\\n--mount type=bind,src=`pwd`,dst=/usr/src/app \\\ndocker_demo:latest</code></pre></div>\n<p>But this could conflict with our host machine’s <code class=\"language-text\">node_modules</code> folder since we’re mounting our entire <code class=\"language-text\">pwd</code> to our app’s location in the image (in case one of our developers accidentally runs <code class=\"language-text\">npm install</code> on their host machine). So, let’s add a volume to ensure we preserve the <code class=\"language-text\">node_modules</code> that exists within our container.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">docker run --rm -it \\\n--name docker_demo_container \\\n--mount type=bind,src=`pwd`,dst=/usr/src/app \\\n--volume /usr/src/app/node_modules \\\ndocker_demo:latest</code></pre></div>\n<h4>Accessing Ports Inside the Container</h4>\n<p>If you tried the above command (and you’re running a VueJS app), you should see:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\"> App running at:\n  - Local:   http://localhost:8080/\n\n  It seems you are running Vue CLI inside a container.\n  Access the dev server via http://localhost:&lt;your container&#39;s external mapped port&gt;/</code></pre></div>\n<p>Docker is giving you a hint that we need to <strong>expose</strong> a port from our container and <strong>publish</strong> it on our host machine. We do this by adding the <code class=\"language-text\">--publish</code> flag to our run command. (We already have the <code class=\"language-text\">EXPOSE</code> command in our <code class=\"language-text\">Dockerfile.dev</code>)</p>\n<blockquote>\n<p><strong><code class=\"language-text\">--publish &lt;host-port&gt;:&lt;container-port&gt;</code></strong> tells Docker that traffic to the host machine (i.e. via localhost) on port <code class=\"language-text\">&lt;host-port&gt;</code> should be directed towards the container at the <code class=\"language-text\">&lt;container-port&gt;</code> that you define.</p>\n</blockquote>\n<h3><code class=\"language-text\">docker run</code> in One Command</h3>\n<p>Let’s take a look at our final run command:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">docker run --rm -it \\\n--name docker_demo_container \\\n--publish 4200:8080 \\\n--mount type=bind,src=`pwd`,dst=/usr/src/app \\\n--volume /usr/src/app/node_modules \\\ndocker_demo:latest</code></pre></div>\n<p><img src=\"/4873969bc8c731ac6bbc8b7ab8ccd9b8/03-full-docker-run.gif\" alt=\"Running the Docker Image we Built Success\" title=\"Successfully Running a Docker container of a Custom Image\"></p>\n<p>Running the above command will finally allow us to access our app via <a href=\"http://localhost:4200\">http://localhost:4200</a>.</p>\n<h3>Testing it out</h3>\n<p>Let’s build a fresh copy and run it. If you try changing one of our file’s templates, you’ll see everything is still functioning as it should be.</p>\n<p>But speaking of testing, what about unit tests? Well, once our container is running, we can open a new terminal and <code class=\"language-text\">docker exec</code> a command to run in our container.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">docker exec -it docker_demo_container npm run test:unit</code></pre></div>\n<p><img src=\"/b430efccafd8fda450f4136be75e287c/03-docker-exec.gif\" alt=\"Running Unit Tests through Docker\" title=\"Running Unit Tests in Docker Container\"></p>\n<p>The above command will create an interactive terminal connection with our container <code class=\"language-text\">docker_demo_container</code> and execute the command <code class=\"language-text\">npm run test:unit</code> in it, allowing us to run unit tests for our app.</p>\n<h2>In Closing</h2>\n<p>We now have a way to build our development images and run them locally while maintaining the conveniences of Hot Module Replacement to keep our development workflow efficient. Our developers don’t need to worry about dependencies on their host machine colliding with those in the image. No more “but it works on my machine” excuses. And, we also have a command we can easily run to execute our unit tests.</p>\n<p>If you find anything I missed or want to chat more about Docker, please reach out to me!</p>","frontmatter":{"title":"Docker for Frontend Devs - Custom Docker Images for Development","date":"May 08, 2019","description":"Build a custom Docker image for your application! Serve your application from a Docker container. Learn what Docker is and find several opportunities for cutting down on image size."}}},"pageContext":{"slug":"/03-docker-creating-custom-docker-images/","previous":{"fields":{"slug":"/02-docker-learning-the-command-line/"},"frontmatter":{"title":"Docker for Frontend Devs - Learning the Command Line Interface"}},"next":{"fields":{"slug":"/04-docker-continuous-integration-and-e2e-with-cypress/"},"frontmatter":{"title":"Docker Full Circle: Continuous Integration (CI) with Cypress"}}}},"staticQueryHashes":["240262808","2841359383"]}