<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel R</title>
    <description>The latest articles on DEV Community by Daniel R (@danielres).</description>
    <link>https://dev.to/danielres</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F175379%2Feed6b021-bd4d-4633-9afd-a57bba097a52.jpeg</url>
      <title>DEV Community: Daniel R</title>
      <link>https://dev.to/danielres</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielres"/>
    <language>en</language>
    <item>
      <title>Simplify development &amp; deployments with docker + docker-compose</title>
      <dc:creator>Daniel R</dc:creator>
      <pubDate>Thu, 08 Oct 2020 21:57:58 +0000</pubDate>
      <link>https://dev.to/danielres/simplify-development-deployments-with-docker-docker-compose-9ji</link>
      <guid>https://dev.to/danielres/simplify-development-deployments-with-docker-docker-compose-9ji</guid>
      <description>&lt;p&gt;It is very common nowadays to develop projects made of multiple services. The most common example is to have at least a backend and a frontend as separate apps. And in more complex projects, we can have many more services, all running in parallel.&lt;/p&gt;

&lt;p&gt;A developer often has to run these services simultaneously on their local machine.&lt;/p&gt;

&lt;p&gt;The old way to do this is to simply start each service manually in a separate terminal. But this can quickly become cumbersome, as you may have experienced.&lt;/p&gt;

&lt;p&gt;Some popular tools like &lt;code&gt;concurrently&lt;/code&gt; or &lt;code&gt;npm-run-all&lt;/code&gt; make this easier, at the cost of adding dependencies. Combined with &lt;code&gt;yarn workspaces&lt;/code&gt; or &lt;code&gt;lerna&lt;/code&gt;, they allow for pretty smooth developer experience.&lt;/p&gt;

&lt;p&gt;Thanks to these tools, a developer can type a unique command, for example &lt;code&gt;yarn dev&lt;/code&gt; and have their whole stack with all services started automatically. And a single &lt;code&gt;CTRL+c&lt;/code&gt; in the terminal allows to terminate all services in one single move. Really nice right?&lt;/p&gt;

&lt;p&gt;There are however some cons with this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More complex npm scripts in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;New dependencies added to the project, that need to be maintained&lt;/li&gt;
&lt;li&gt;A sort of mixed concerns, where the project's code is now not only used to build services but also to orchestrate them&lt;/li&gt;
&lt;li&gt;If you use &lt;code&gt;yarn-workspaces&lt;/code&gt;: each service now has to use yarn as well. You get some sort of vendor lock-in that couples your services together. And what if we want different languages per service?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A better approach with docker and docker-compose
&lt;/h2&gt;

&lt;p&gt;After using &lt;code&gt;yarn-workspaces&lt;/code&gt; in conjunction with &lt;code&gt;npm-run-all&lt;/code&gt; for a while for all my projects, I've switched recently to just using &lt;code&gt;docker&lt;/code&gt; with &lt;code&gt;docker-compose&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As I've discovered, &lt;code&gt;docker-compose&lt;/code&gt; can achieve all the above, and way more:&lt;/p&gt;

&lt;p&gt;✔️ Running all services concurrently&lt;br&gt;
✔️ No extra npm dependencies with their added complexity: no &lt;code&gt;concurrently&lt;/code&gt;, no &lt;code&gt;npm-run-all&lt;/code&gt;, no &lt;code&gt;yarn workspaces&lt;/code&gt; and such&lt;br&gt;
✔️ 100% separated and independent services, just standard apps&lt;br&gt;
✔️ Ability to use a different language for each service, different node versions, or package managers&lt;br&gt;
✔️ A simpler mental model&lt;/p&gt;

&lt;p&gt;On top of that, by using not only &lt;code&gt;docker-compose&lt;/code&gt; but also a separate &lt;code&gt;Dockerfile&lt;/code&gt; for each service, then using &lt;code&gt;docker-compose&lt;/code&gt; for orchestration in development, we gain tremendous advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ability to use the exact same stack in all environments: development, staging, production,(...) and across the whole CD/CI pipeline.&lt;/li&gt;
&lt;li&gt;Extremely easy replication of the development environment on any machine. A new developer needs only &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;docker-compose&lt;/code&gt; to start working. &lt;strong&gt;No more time lost in re-building a dev environment!&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It doesn't matter if your services need different node versions, or ruby, python, clojure, databases, cobol,... &lt;strong&gt;Everything can be spun up on a pristine machine with just 2 commands&lt;/strong&gt;: an initial &lt;code&gt;docker-compose build&lt;/code&gt;, then just a daily &lt;code&gt;docker-compose up&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Let's say we have a stack made of a frontend and a backend, both in javascript.&lt;/p&gt;

&lt;p&gt;Here's our project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-app
  - Readme.md

  - backend
    - Dockerfile
    - package.json
    - ...

  - frontend
    - Dockerfile
    - package.json
    - ...

  - dev
    - docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each service could be in a different language, node version, package manager,...&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose.yml&lt;/code&gt; could perfectly be in the project's root folder. I just like to create a new &lt;code&gt;dev&lt;/code&gt; folder to group all dev related tools. It also helps clarify to all developers (even myself) that this &lt;code&gt;docker-compose.yml&lt;/code&gt; file is just for development use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  backend/Dockerfile
&lt;/h3&gt;

&lt;p&gt;Our &lt;code&gt;backend/Dockerfile&lt;/code&gt; is written with the production environment in mind, for example, the instructions &lt;code&gt;RUN yarn --prod --frozen-lockfile&lt;/code&gt; and &lt;code&gt;CMD [ "yarn", "start" ]&lt;/code&gt; are for production, but &lt;code&gt;docker-compose&lt;/code&gt; will allow us later to override some parts locally to meet our development needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# backend/Dockerfile =================&lt;/span&gt;
&lt;span class="c"&gt;# (production-friendly)&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:14-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="c"&gt;# Copy these files from your host into the image&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; yarn.lock .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json .&lt;/span&gt;

&lt;span class="c"&gt;# Run the command inside the image filesystem&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nt"&gt;--prod&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;

&lt;span class="c"&gt;# Copy the rest of your app's source code from your host to the image filesystem:&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Which port is the container listening on at runtime?&lt;/span&gt;
&lt;span class="c"&gt;# This should be the same port your server is listening to:&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="c"&gt;# Start the server within the container:&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "yarn", "start" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  frontend/Dockerfile
&lt;/h3&gt;

&lt;p&gt;Almost identical to our backend &lt;code&gt;Dockerfile&lt;/code&gt;, also written for production in mind. &lt;br&gt;
&lt;code&gt;docker-compose&lt;/code&gt; will allow us to override some instructions locally, just for development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# frontend/Dockerfile =================&lt;/span&gt;
&lt;span class="c"&gt;# (production-friendly)&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:14-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; yarn.lock .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nt"&gt;--prod&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "yarn", "start" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  dev/docker-compose.yml
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;version: "3"

services:
  backend:
    build: "../backend"
    ports:
      - 8080:8080
    command: sh -c "yarn &amp;amp;&amp;amp; yarn dev"
    volumes:
      - ../backend:/usr/src/app

  frontend:
    build: "../frontend"
    ports:
      - 3000:3000
    command: sh -c "yarn &amp;amp;&amp;amp; yarn dev"
    volumes:
      - ../frontend:/usr/src/app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here, while reusing the 2 previously defined &lt;code&gt;Dockerfile&lt;/code&gt;,  we are allowed to override certain commands and parameters.&lt;/p&gt;

&lt;p&gt;In this case, &lt;code&gt;ports&lt;/code&gt; and &lt;code&gt;command&lt;/code&gt; override the values of &lt;code&gt;EXPOSE&lt;/code&gt; and &lt;code&gt;CMD&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;volumes&lt;/code&gt; allow us to map the frontend and backend folders on our machine to the ones inside the containers. It means that you can now edit the project files normally in your IDE, all changes being reflected instantly inside the containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Booting up the whole project
&lt;/h2&gt;

&lt;p&gt;For the first run, in a terminal, just type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;dev
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will download the images defined in the &lt;code&gt;Dockerfile&lt;/code&gt;s (&lt;code&gt;node:14-alpine&lt;/code&gt;) and prepare the whole environment for both frontend and backend.&lt;/p&gt;

&lt;p&gt;Note that you need to run this command only once initially, or after modifying a &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To run the whole stack and start coding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;dev
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You should now be able to visit your frontend on &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; and your backend on &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Daily usage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;From now on, all npm scripts and commands should be executed from within the containers, not on the host machine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, if we want to add the package &lt;code&gt;classnames&lt;/code&gt; to the frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# in a new terminal:&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;dev
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose &lt;span class="nb"&gt;exec &lt;/span&gt;frontend yarn add classnames
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Phew!&lt;/strong&gt; This is cumbersome, and a lot of typing, to be honest, don't you think? &lt;br&gt;
Don't worry, we'll see how to make it better in the next section:&lt;/p&gt;
&lt;h2&gt;
  
  
  And now we can have nice things!
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1) Fewer keystrokes thanks to bash aliases
&lt;/h3&gt;

&lt;p&gt;Who enjoys long cumbersome typing? No one. &lt;/p&gt;

&lt;p&gt;Here's one simple solution: let's add an &lt;code&gt;aliases.sh&lt;/code&gt; file under &lt;code&gt;dev&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-app
  - dev
    - aliases.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# my-app/dev/aliases.sh&lt;/span&gt;

&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;be&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"docker-compose exec backend"&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;fe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"docker-compose exec frontend"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And let's source it in the current terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; dev/aliases.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;From now on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# we can type this:&lt;/span&gt;
  &lt;span class="nv"&gt;$ &lt;/span&gt;fe yarn add classnames
  &lt;span class="nv"&gt;$ &lt;/span&gt;be yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; nodemon

&lt;span class="c"&gt;# instead of:&lt;/span&gt;
&lt;span class="c"&gt;#   $ docker-compose exec frontend yarn add classnames&lt;/span&gt;
&lt;span class="c"&gt;#   $ docker-compose exec backend yarn add -D nodemon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To avoid sourcing manually in every terminal, we can also do it once and for all in &lt;code&gt;.bashrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# in /home/&amp;lt;USER&amp;gt;/.bashrc&lt;/span&gt;
&lt;span class="c"&gt;# at the very end, just add this line:&lt;/span&gt;

&lt;span class="nb"&gt;.&lt;/span&gt; /&amp;lt;PATH_TO_MY_APP&amp;gt;/dev/aliases.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I would recommend doing this only when working continuously on a project, and removing this new line once it's not needed anymore.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) More reliable deployments into staging, production,...
&lt;/h3&gt;

&lt;p&gt;Thanks to the &lt;code&gt;Dockerfile&lt;/code&gt;s (written for production, remember?), we can run our services within the exact same OS and context under all our environments: development, test, staging, production,...&lt;/p&gt;

&lt;p&gt;For example, if you use &lt;a href="https://cloud.google.com/run"&gt;Google Cloud Run&lt;/a&gt;, you can now provide it the &lt;code&gt;Dockerfile&lt;/code&gt; for each service, and be assured that if your code runs fine locally, it should also run perfectly once deployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Benefits that come with docker-compose
&lt;/h3&gt;

&lt;p&gt;For example, it is now very easy to add additional container depending on your projects needs.&lt;/p&gt;

&lt;p&gt;Let's say we need a &lt;code&gt;postgres&lt;/code&gt; database in version 11.1 for development. We can just add it to &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;version: &lt;span class="s2"&gt;"3"&lt;/span&gt;

services:
  backend:
    build: &lt;span class="s2"&gt;"../backend"&lt;/span&gt;
    ports:
      - 8080:8080
    &lt;span class="nb"&gt;command&lt;/span&gt;: sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"yarn &amp;amp;&amp;amp; yarn dev"&lt;/span&gt;
    volumes:
      - ../backend:/usr/src/app

  frontend:
    build: &lt;span class="s2"&gt;"../frontend"&lt;/span&gt;
    ports:
      - 3000:3000
    &lt;span class="nb"&gt;command&lt;/span&gt;: sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"yarn &amp;amp;&amp;amp; yarn dev"&lt;/span&gt;
    volumes:
      - ../frontend:/usr/src/app

  db:
    image: postgres:11.1
    &lt;span class="nb"&gt;command&lt;/span&gt;: &lt;span class="s2"&gt;"-c logging_collector=on"&lt;/span&gt;
    restart: always
    ports:
      - 5432:5432
    environment:
      POSTGRES_PASSWORD: changeme
      POSTGRES_USER: changeme
      POSTGRES_DB: changeme

  &lt;span class="c"&gt;# Let's also provide an admin UI for the postgres &lt;/span&gt;
  &lt;span class="c"&gt;# database, often useful during development:&lt;/span&gt;

  adminer:
    image: adminer
    restart: always
    ports:
      - 5000:8080
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We have seen how we can develop multiple concurrently running services, each in any language, with any kind of database, on any machine, without installing any of these on the host machine itself. &lt;/p&gt;

&lt;p&gt;We just need to install &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;docker-compose&lt;/code&gt; (and an IDE), and that's it! &lt;/p&gt;

&lt;p&gt;With this approach, each service is just a perfectly contained regular app.&lt;/p&gt;

&lt;p&gt;Furthermore, we can now run each service within the exact same system (OS) across all environments and all developer's machines.&lt;/p&gt;

&lt;p&gt;On-boarding new developers and setting up their development environment can traditionally take days. With this approach, it's a matter of minutes.&lt;/p&gt;

&lt;p&gt;It also makes it near instantaneous to switch between different projects written in different languages or language versions.&lt;/p&gt;

</description>
      <category>intermediate</category>
      <category>docker</category>
      <category>devops</category>
      <category>development</category>
    </item>
  </channel>
</rss>
