<?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: Cadu Ribeiro</title>
    <description>The latest articles on DEV Community by Cadu Ribeiro (@caduribeiro).</description>
    <link>https://dev.to/caduribeiro</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%2F88411%2Fe460ca80-d4e0-4dbe-9a2a-6d2457764788.jpeg</url>
      <title>DEV Community: Cadu Ribeiro</title>
      <link>https://dev.to/caduribeiro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/caduribeiro"/>
    <language>en</language>
    <item>
      <title>Running Neovim with Devcontainers</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Thu, 16 May 2024 15:00:00 +0000</pubDate>
      <link>https://dev.to/caduribeiro/running-neovim-with-devcontainers-2imn</link>
      <guid>https://dev.to/caduribeiro/running-neovim-with-devcontainers-2imn</guid>
      <description>&lt;p&gt;In this post, I will show you how you can use Neovim with DevContainers to simplify the development environment setup. DevContainers is an open specificationthat that allows containers to be used as a complete development environment with all tools necessary for the development lifecycle. To read more about DevContainers, check my previous post &lt;a href="https://dev.to/caduribeiro/using-devcontainers-to-set-up-your-development-environment-5fbp"&gt;here&lt;/a&gt; or the official documentation &lt;a href="https://containers.dev/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One thing that most people don’t know is that DevContainers is not only for VSCode. It is a specification that can be used with any editor or IDE (although the integration with VSCode has the best support).&lt;/p&gt;

&lt;p&gt;For example, the video below shows DevContainers being used with RubyMine:&lt;/p&gt;


  
  
  Your browser does not support the video tag.


&lt;p&gt;Let’s see how we can use Neovim with DevContainers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The example project
&lt;/h2&gt;

&lt;p&gt;We need Docker installed on our machine to run DevContainers. If you don’t have Docker installed, you can install it by following the instructions &lt;a href="https://docs.docker.com/get-docker/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We will create a new Ruby on Rails project for this example and use the new &lt;a href="https://github.com/rails/rails-new"&gt;rails-new&lt;/a&gt; tool to generate the project. This tool is a new way to generate Rails applications even when you don’t have Ruby installed on your machine, as it uses Docker to run the generator (useful for people who don’t want to install Ruby on their machines and rely only on Docker). It is still in the experimental phase, but it is already usable. See &lt;a href="https://github.com/rails/rails-new?tab=readme-ov-file#installation"&gt;here&lt;/a&gt; for installation instructions.&lt;/p&gt;

&lt;p&gt;I’ve installed the &lt;code&gt;rails-new&lt;/code&gt; tool on my machine using Cargo (the Rust package manager). If you don’t have Cargo installed, you can install it by following the instructions &lt;a href="https://doc.rust-lang.org/cargo/getting-started/installation.html"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo install --git https://github.com/rails/rails-new

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let’s create a new Rails project using the &lt;code&gt;rails-new&lt;/code&gt; tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails-new nvim-devcontainer-post -- --main -d postgresql

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create a new Rails project named &lt;code&gt;nvim-devcontainer-post&lt;/code&gt; with the &lt;code&gt;--main&lt;/code&gt; flag, which will use Rails’ main branch instead of the regular releases version, because the Rails on the main branch already generates a project with DevContainers configured by default (This will be available on Rails 8). The &lt;code&gt;-d postgresql&lt;/code&gt; flag will configure the project to use PostgreSQL as the database.&lt;/p&gt;

&lt;p&gt;After running this command, you will have a new Rails project with DevContainers configured. You can check that the &lt;code&gt;.devcontainer&lt;/code&gt; folder was created with the necessary files for DevContainers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ nvim-devcontainer-post git:(main) ✗ ls .devcontainer
Dockerfile compose.yaml devcontainer.json

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our project is ready for us to start using Neovim with DevContainers. You can even open the project in VSCode and see that the DevContainers is working:&lt;/p&gt;


  
  
  Your browser does not support the video tag.


&lt;p&gt;Let’s destroy these containers so we can install more tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose -f .devcontainer/compose.yaml down

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing Neovim in the DevContainer
&lt;/h2&gt;

&lt;p&gt;The way DevContainers work is by running the editor or IDE server inside the container, this way the editor or IDE can access all the tools installed in the container like Ruby, the language server (LSP), linting and formatting tools, etc. This is how VSCode and RubyMine work with DevContainers, they have their server running inside the container and communicate with the editor running on the host machine via remote editing. This is why we need to install Neovim in the DevContainer to use it with DevContainers.&lt;/p&gt;

&lt;p&gt;If you read my previous post about DevContainers, you know that we can install additional tools in the DevContainer by adding them to the &lt;code&gt;Dockerfile&lt;/code&gt; or even using &lt;a href="https://containers.dev/implementors/features/"&gt;DevContainer’s Features&lt;/a&gt; to install them. Both ways are valid, but I prefer to install them via Features.&lt;/p&gt;

&lt;p&gt;I’ve created a repository with Features to install Neovim and Tmux in the DevContainer. You can check the repository &lt;a href="https://github.com/duduribeiro/devcontainer-features"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, let’s install Neovim in our DevContainer using the Features. First, we need to edit the &lt;code&gt;.devcontainer/devcontainer.json&lt;/code&gt; file and add the following content to the &lt;code&gt;features&lt;/code&gt; key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "features": {
    "ghcr.io/devcontainers/features/github-cli:1": {},
    "ghcr.io/rails/devcontainer/features/activestorage": {},
    "ghcr.io/rails/devcontainer/features/postgres-client": {},
    "ghcr.io/duduribeiro/devcontainer-features/neovim:1": { "version": "nightly" },
  },

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve added the &lt;code&gt;neovim&lt;/code&gt; Feature to the &lt;code&gt;features&lt;/code&gt; key, the other three Features are the default Features that Rails added to the project when we created the project. I’ve used the &lt;code&gt;nightly&lt;/code&gt; version of Neovim, because my Neovim configuration uses some features that are only available in the nightly version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the DevContainer
&lt;/h2&gt;

&lt;p&gt;How can we start the DevContainer without needing to open the project in VSCode? We can use the &lt;a href="https://github.com/devcontainers/cli"&gt;devcontainers-cli&lt;/a&gt; to start the DevContainer from the command line without needing to open the project in VSCode.&lt;/p&gt;

&lt;p&gt;You can install the &lt;code&gt;devcontainers-cli&lt;/code&gt; by running the following command: (check &lt;a href="https://github.com/devcontainers/cli?tab=readme-ov-file#try-it-out"&gt;here&lt;/a&gt; for more information on the installation process)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g @devcontainers/cli

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;devcontainers-cli&lt;/code&gt; is a CLI tool that allows you to control DevContainers from the command line. It is still missing some features (like stopping the DevContainer), but you can already use it to start and execute commands in the DevContainer.&lt;/p&gt;

&lt;p&gt;Let’s build and start our DevContainer using the &lt;code&gt;devcontainers-cli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;devcontainer build --workspace-folder .

....

devcontainer up --workspace-folder .

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will build the DevContainer and start it. After running this command you will receive the message with the &lt;code&gt;outcome&lt;/code&gt; status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[+] Running 4/4
 ✔ Container nvim_devcontainer_post-redis-1 Started 0.0s
 ✔ Container nvim_devcontainer_post-postgres-1 Started 0.0s
 ✔ Container nvim_devcontainer_post-selenium-1 Started 0.0s
 ✔ Container nvim_devcontainer_post-rails-app-1 Started 0.0s
{"outcome":"success", ...}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that the DevContainer was started successfully. Now we can execute commands in the DevContainer using the &lt;code&gt;devcontainer exec&lt;/code&gt; command, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;devcontainer exec --workspace-folder . ls

Dockerfile Gemfile Gemfile.lock README.md Rakefile  app bin config config.ru db lib log public storage test tmp vendor

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we see that the &lt;code&gt;ls&lt;/code&gt; command was executed in the DevContainer and we received the output with the files in the root of the project.&lt;/p&gt;

&lt;p&gt;Different from VSCode or RubyMine that have a client running on the host machine that communicates with the server running in the container, Neovim will run inside the container and we access it via the terminal. This may can change in the future if they implement a remote editing feature that allows us to run the server in the container and the client on the host machine, but for now, we need to run Neovim using the devcontainer-cli.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;devcontainer exec --workspace-folder . nvim

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neovim is now running inside the DevContainer and we can use it to edit files in the project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b_5WnFUL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cadu.dev/assets/images/nvim-running-on-devcontainer-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b_5WnFUL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cadu.dev/assets/images/nvim-running-on-devcontainer-1.png" alt="neovim running in the devcontainer" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But this is not using any configuration or plugins because we are running Neovim in the DevContainer and we don’t have any configuration files or plugins installed there. You can install your Neovim configuration in the DevContainer manually via terminal but everytime that you need to rebuild the container you will need to install it again. A solution for this is to copy your Neovim configuration from your host machine to the DevContainer.&lt;/p&gt;

&lt;p&gt;I have my &lt;a href="https://github.com/duduribeiro/dotfiles"&gt;Neovim configurations&lt;/a&gt; in my machine at ~/.config/nvim, so I can copy it to the DevContainer during the start process. Let’s see how we can do this.&lt;/p&gt;

&lt;p&gt;Before, let’s stop our containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose -f .devcontainer/compose.yaml down

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Copying Neovim configuration to the DevContainer
&lt;/h2&gt;

&lt;p&gt;To copy the Neovim configuration from the host machine to the DevContainer, we can specify mount points during the &lt;code&gt;devcontainer up&lt;/code&gt; command. This is how I do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;devcontainer up --mount "type=bind,source=$HOME/.config/nvim,target=/home/vscode/.config/nvim" --workspace-folder .

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DevContainers has a way to specify mount points on the .devcontainer/devcontainer.json file (see &lt;a href="https://containers.dev/implementors/json_reference/"&gt;the json reference&lt;/a&gt; and look for mounts) but it didn’t work for me, so I use the &lt;code&gt;--mount&lt;/code&gt; flag in the &lt;code&gt;devcontainer up&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;This command will mount the &lt;code&gt;~/.config/nvim&lt;/code&gt; folder from the host machine to the &lt;code&gt;/home/vscode/.config/nvim&lt;/code&gt; folder in the DevContainer. Now we can run Neovim with our configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;devcontainer exec --workspace-folder . nvim

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is how Neovim is running with my configuration in the DevContainer, with all my plugins and settings and even running the LSP:&lt;/p&gt;


  
  
  Your browser does not support the video tag.


&lt;p&gt;This is how I use Neovim with DevContainers and I hope this post helps you with this config too. DevContainers is a great tool to simplify the development environment setup and I think (and hope) that most editors and IDEs will support it in the future.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>vim</category>
      <category>devcontainers</category>
      <category>developmentenvironment</category>
    </item>
    <item>
      <title>Using Devcontainers to set up your development environment</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Thu, 28 Sep 2023 15:00:00 +0000</pubDate>
      <link>https://dev.to/caduribeiro/using-devcontainers-to-set-up-your-development-environment-5fbp</link>
      <guid>https://dev.to/caduribeiro/using-devcontainers-to-set-up-your-development-environment-5fbp</guid>
      <description>&lt;p&gt;One common problem in software development is setting up the project’s development environment. Have you ever joined a project, opened the README.md, and found a README HELL, filled with endless instructions on how to configure the project? And halfway through the instructions, you encounter an error while running a command because you’re not on the same operating system version as the person who wrote the README, or because the documentation is outdated.&lt;/p&gt;

&lt;p&gt;Quoting &lt;a href="https://twitter.com/palkan_tula" rel="noopener noreferrer"&gt;Vladimir Dementyev&lt;/a&gt; in his amazing talk &lt;a href="https://speakerdeck.com/palkan/railsconf-2019-terraforming-legacy-rails-applications" rel="noopener noreferrer"&gt;Terraforming Legacy Rails applications:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Developers should be able to run project with the least possible effort&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;for example, cloning the code and running a few scripts for server setup and start.&lt;/p&gt;

&lt;p&gt;GitHub published a &lt;a href="https://github.blog/2015-06-30-scripts-to-rule-them-all/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; in 2015 where they demonstrated how they did this at the time. They state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Having a bootstraping experience for projects reduces friction and encourages contribution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Reduces friction&lt;/code&gt;: This is very interesting when we think about onboarding a new person to the project. Do you remember the past times when a newcomer would take days to get the project up and running on their computer?&lt;/p&gt;

&lt;p&gt;And in this post, GitHub shows how they solved this in 2015: A set of scripts, with a standard naming convention across all projects, that handle dependency installation and updates, project setup, test execution, and environment configuration. They refer to this set of scripts as ‘Scripts to Rule Them All.’&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Foneringtorulethemall.jpg" alt="oneringtorulethemall"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;&lt;a href="https://www.youtube.com/watch?v=HgOha2D5kt8&amp;amp;themeRefresh=1" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=HgOha2D5kt8&amp;amp;themeRefresh=1&lt;/a&gt;&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This model greatly helped new hires at the company configure and start projects within half a day. In &lt;a href="https://github.blog/2021-08-11-githubs-engineering-team-moved-codespaces/" rel="noopener noreferrer"&gt;another post&lt;/a&gt;, they mention that &lt;code&gt;in the vast majority of cases, everything worked without issues&lt;/code&gt; and that &lt;code&gt;when something didn't work, there was a Slack channel called #friction where others debugged and helped resolve system issues&lt;/code&gt;. Event with nvironment setup scripts, issues persisted because the company scripts were based on macOS, while individuals might use different operating systems like Linux or even a more recent macOS version not yet supported by the scripts. These errors continued to create friction when starting a development environment for the project.&lt;/p&gt;

&lt;p&gt;How can we further reduce friction? Enter &lt;code&gt;DevContainers&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are DevContainers?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Development Containers&lt;/code&gt; (or &lt;code&gt;DevContainers&lt;/code&gt;) is an &lt;a href="https://containers.dev/" rel="noopener noreferrer"&gt;open specification&lt;/a&gt; that allows containers to be used as a complete development environment, enabling us to run our applications, dependencies like databases and messaging services, and other tools necessary for the development lifecycle. DevContainers can be run locally or in a remote environment (including services like &lt;a href="https://github.com/features/codespaces" rel="noopener noreferrer"&gt;GitHub Codespaces&lt;/a&gt; and &lt;a href="https://gitpod.io/" rel="noopener noreferrer"&gt;GitPod&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In this text, I won’t enter into the basics of containers/docker. If you’d like to learn and know more, please visit &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;https://www.docker.com/&lt;/a&gt; and &lt;a href="https://cloud.google.com/learn/what-are-containers" rel="noopener noreferrer"&gt;https://cloud.google.com/learn/what-are-containers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DevContainers&lt;/code&gt; specification states that your project have a folder called &lt;code&gt;.devcontainer&lt;/code&gt; with a &lt;code&gt;devcontainer.json&lt;/code&gt; file. To read the complete specification, visit &lt;a href="https://containers.dev/implementors/json_reference/" rel="noopener noreferrer"&gt;this link&lt;/a&gt;. In summary, the file contains the image (or Dockerfile) to be used, the container’s forwarded ports, specific product customizations (e.g., installing extensions by default in VSCode), and more.&lt;/p&gt;

&lt;p&gt;One of the inclusions in the specification is the &lt;code&gt;DevContainer Features&lt;/code&gt;, which are independent and shareable units containing installation or configuration code for the container. These features are installed on top of the image used in the DevContainer. The idea behind features is to easily add more tools and libraries to the DevContainer. The following example demonstrates how to install the &lt;code&gt;GitHub CLI&lt;/code&gt;, for instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .devcontainer/devcontainer.json
{
  "name": "MyApp DevContainer",
  //...
  "features": {
    "ghcr.io/devcontainers/features/github-cli:1": {
        "version": "latest"
    }  
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A list of existing &lt;code&gt;Features&lt;/code&gt; can be found &lt;a href="https://containers.dev/features" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages and disadvantages of using DevContainers
&lt;/h2&gt;

&lt;p&gt;One of the evident advantages we observe in using DevContainers is reducing friction in project setup. Having this reproducible development environment ensures that everyone on the team is using the same environment, making project setup easier. This will make new team members onboarding much faster and easier. It’s common that we don’t see problem with slow onboarding because we assume it will only happen once, which is not always true (maybe you got a brandly new computer?). Another advantage is that no matter which operating system you are using, the development environment will work, and you don’t need separate instructions in the docs (no more “if you are on Linux, do this” in your README).&lt;/p&gt;

&lt;p&gt;But of course, this approach may have some disadvantages. Possible speed reduction (especially on macOS). We know that Docker on macOS runs on top of a virtual machine, which impacts performance a bit, as discussed in &lt;a href="https://www.cncf.io/blog/2023/02/02/docker-on-macos-is-slow-and-how-to-fix-it/" rel="noopener noreferrer"&gt;this article&lt;/a&gt;. There are some alternatives that promise to improve Docker’s speed on macOS, such as &lt;a href="https://github.com/abiosoft/colima" rel="noopener noreferrer"&gt;Colima&lt;/a&gt; and &lt;a href="https://orbstack.dev" rel="noopener noreferrer"&gt;OrbStack&lt;/a&gt;. My friend &lt;a href="https://twitter.com/fvztdk" rel="noopener noreferrer"&gt;Felipe Vaz&lt;/a&gt; is using Colima and said it is good. I am currently testing OrbStack based on a recommendation from &lt;a href="https://twitter.com/robzolkos" rel="noopener noreferrer"&gt;Rob Zolkos&lt;/a&gt;. &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Frobzolkos-orbstack-post.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Frobzolkos-orbstack-post.png" alt="rob zolkos Twitter Post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, stuff that works well, such as integration tests with browsers (e.g., using Selenium), can be a trickier to set up and get working perfectly.&lt;/p&gt;

&lt;p&gt;As with everything in software development, it’s a tradeoff, and you should consider the pros and cons to see if it’s worth it in your case. For me, the advantages outweigh the disadvantages, and it’s worth using. It may not be the case for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Containers vs. DevContainers
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If I already have containers and a Dockerfile for my production app running, why not use them instead of a separate container for development?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we talk about reproducible environments with DevContainers, this question often arises, and it’s a good question. One of the &lt;a href="https://12factor.net/dev-prod-parity" rel="noopener noreferrer"&gt;12factors&lt;/a&gt; suggests having parity between development and production, which leads us to try using the same production container image in development. However, note that this advice emphasizes making environments &lt;code&gt;as similar as POSSIBLE&lt;/code&gt;, not &lt;code&gt;EXACTLY THE SAME&lt;/code&gt;.”&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep development, staging, and production as similar AS POSSIBLE&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a subtle distinction but makes a difference when we deploy our development containers. One of the points that the “Dev/prod parity” factor addresses is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://12factor.net/backing-services" rel="noopener noreferrer"&gt;Backing services&lt;/a&gt;, such as the app’s database, queueing system, or cache, is one area where dev/prod parity is important. Many languages offer libraries which simplify access to the backing service, including &lt;em&gt;adapters&lt;/em&gt; to different types of services….&lt;/p&gt;

&lt;p&gt;Developers sometimes find great appeal in using a lightweight backing service in their local environments, while a more serious and robust backing service will be used in production. For example, using SQLite locally and PostgreSQL in production; or local process memory for caching in development and Memcached in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The twelve-factor developer resists the urge to use different backing services between development and production&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that’s the idea that &lt;code&gt;Dev/prod parity&lt;/code&gt; brings. Try to make your development environment as similar as POSSIBLE to production. If you use PostgreSQL in production, don’t use SQLite as your development database because there are differences between them that can cause compatibility issues in your application, and you’ll only notice them in production. Note that the primary focus of &lt;code&gt;Dev/prod parity&lt;/code&gt; is on using different backing services, not telling you to use the SAME IMAGE used in production for development.&lt;/p&gt;

&lt;p&gt;Production containers have different requirements from development containers!&lt;/p&gt;

&lt;p&gt;When we think about deploying our application in containers, we have concern such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attempting to minimize the size of the final container image as much as possible&lt;/li&gt;
&lt;li&gt;Having as few dependencies as possible&lt;/li&gt;
&lt;li&gt;Exposing the minimum number of open ports&lt;/li&gt;
&lt;li&gt;Reducing the application’s memory consumption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Furthermore, the images we use as a base for our production containers are based on very small and highly suitable images for a production environment (such as debian-slim or alpine), but they are not as suitable for a development environment. (You’ll want to run &lt;code&gt;fzf&lt;/code&gt; in your DevContainer, but it doesn’t make sense to have it in your production image, for example.)&lt;/p&gt;

&lt;p&gt;In the development environment, we want a complete system (such as ubuntu or debian) with various utilities and auxiliary tools to assist the daily work of project contributors (e.g., installing &lt;code&gt;fzf&lt;/code&gt; for searching, &lt;code&gt;vim&lt;/code&gt; for quick file editing, a more comprehensive shell with multiple auto-completions) and we can leave more ports open to facilitate application debugging.&lt;/p&gt;

&lt;p&gt;On the &lt;a href="https://containers.dev/overview#Development-vs-production" rel="noopener noreferrer"&gt;Overview page of DevContainers&lt;/a&gt;, in the &lt;code&gt;Development vs production&lt;/code&gt; section, you can find the following passage:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While deployment and development containers may resemble one another, you may not want to include tools in a deployment image that you use during development.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdev-container-stages.png" alt="different images for different stages of your dev lifecycle"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;&lt;a href="https://containers.dev/overview#Development-vs-production" rel="noopener noreferrer"&gt;https://containers.dev/overview#Development-vs-production&lt;/a&gt;&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;In the image, we can see that a DevContainer for a development environment (inner loop) can include various things that are not necessary for production. In fact, we can consider that in the Outer loop (CI), this DevContainer can be used with even fewer items included.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Considering these points, it might make sense to have two container definitions, one for production and one for development. To achieve this, I like to have two &lt;code&gt;Dockerfile&lt;/code&gt; definitions. One at the root of the project to define the production image and another inside &lt;code&gt;.devcontainer&lt;/code&gt; for the development environment using DevContainers.&lt;/p&gt;

&lt;p&gt;So, the project structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% tree -a | grep Dockerfile -C 1
├── .devcontainer
│   ├── Dockerfile
--
├── Dockerfile

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Example: A Ruby on Rails Application
&lt;/h2&gt;

&lt;p&gt;For this practical example, I’ll be using a Ruby on Rails application. It’s also necessary to have prior knowledge of containers, Docker, and Docker Compose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/duduribeiro/devcontainer-rails-demo" rel="noopener noreferrer"&gt;This GitHub repository&lt;/a&gt; contains the source code used in this post and it’s broken down into &lt;a href="https://github.com/duduribeiro/devcontainer-rails-demo/tags" rel="noopener noreferrer"&gt;tags&lt;/a&gt; containing the progress. The tag &lt;a href="https://github.com/duduribeiro/devcontainer-rails-demo/releases/tag/0-initial" rel="noopener noreferrer"&gt;0-initial&lt;/a&gt; contains the base application used from here on.&lt;/p&gt;

&lt;p&gt;This application was generated using Rails 7.1. Rails 7.1 &lt;a href="https://edgeguides.rubyonrails.org/7_1_release_notes.html#generate-dockerfiles-for-new-rails-applications" rel="noopener noreferrer"&gt;introduced a feature&lt;/a&gt; that generates a &lt;code&gt;Dockerfile&lt;/code&gt; by default when you create a new application. However, this &lt;code&gt;Dockerfile&lt;/code&gt; is optimized for production, as stated in the release note:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s important to note that these files are not meant for development purposes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As we’ve seen earlier, the purpose of a &lt;code&gt;Dockerfile&lt;/code&gt; for the development environment is different from one for production. So, let’s create our &lt;code&gt;Dockerfile&lt;/code&gt; that will be used in our &lt;code&gt;DevContainer&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Dockerfile for the DevContainer.
&lt;/h3&gt;

&lt;p&gt;Let’s begin by creating our Dockerfile that will be used in our DevContainer. Inside the .devcontainer folder, I will create my Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .devcontainer/Dockerfile

ARG DEBIAN_FRONTEND=noninteractive
ARG VARIANT=bullseye

FROM mcr.microsoft.com/vscode/devcontainers/base:${VARIANT}

RUN apt-get update &amp;amp;&amp;amp; \
    apt-get -y install --no-install-recommends \
    build-essential gnupg2 tar git zsh libssl-dev zlib1g-dev libyaml-dev curl libreadline-dev \
    postgresql-client libpq-dev \
    imagemagick libjpeg-dev libpng-dev libtiff-dev libwebp-dev libvips \
    tzdata \
    tmux \
    vim

# Install rbenv and ruby
USER vscode

ARG RUBY_VERSION="3.2.2"
RUN git clone https://github.com/rbenv/rbenv.git /home/vscode/.rbenv \
    &amp;amp;&amp;amp; echo '[-f "/home/vscode/.rbenv/bin/rbenv"] &amp;amp;&amp;amp; eval "$(rbenv init - bash)" # rbenv' &amp;gt;&amp;gt; /home/vscode/.zshrc \
    &amp;amp;&amp;amp; echo '[-f "/home/vscode/.rbenv/bin/rbenv"] &amp;amp;&amp;amp; eval "$(rbenv init - bash)" # rbenv' &amp;gt;&amp;gt; /home/vscode/.bashrc \
    &amp;amp;&amp;amp; echo 'export PATH="/home/vscode/.rbenv/bin:$PATH"' &amp;gt;&amp;gt; /home/vscode/.zshrc \
    &amp;amp;&amp;amp; echo 'export PATH="/home/vscode/.rbenv/bin:$PATH"' &amp;gt;&amp;gt; /home/vscode/.bashrc \
    &amp;amp;&amp;amp; mkdir -p /home/vscode/.rbenv/versions \
    &amp;amp;&amp;amp; mkdir -p /home/vscode/.rbenv/plugins \
    &amp;amp;&amp;amp; git clone https://github.com/rbenv/ruby-build.git /home/vscode/.rbenv/plugins/ruby-build

ENV PATH "/home/vscode/.rbenv/bin/:HOME/.rbenv/shims/:$PATH"

RUN rbenv install $RUBY_VERSION &amp;amp;&amp;amp; \
    rbenv global $RUBY_VERSION &amp;amp;&amp;amp; \
    rbenv versions

COPY .devcontainer/welcome.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I won’t go into much detail on how it works but will instead explain the reasons behind some decisions made. To understand more about how a &lt;code&gt;Dockerfile&lt;/code&gt; works, the &lt;a href="https://docs.docker.com/engine/reference/builder/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; is the best reference.&lt;/p&gt;

&lt;h4&gt;
  
  
  The base image used.
&lt;/h4&gt;

&lt;p&gt;In the first instructions, we define which base image we will use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .devcontainer/Dockerfile

ARG VARIANT=bullseye

FROM mcr.microsoft.com/vscode/devcontainers/base:${VARIANT}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.docker.com/engine/reference/builder/#from" rel="noopener noreferrer"&gt;FROM instruction&lt;/a&gt; specifies to Docker which image to use as the base for ours. And here, we’ve already made the first decision. Which one will we use?&lt;/p&gt;

&lt;p&gt;We can use any base image for ours (including using debian directly). So, could we use the ruby:3 image directly? Yes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM ruby:3

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is a valid &lt;code&gt;Dockerfile&lt;/code&gt; to use as a DevContainer, and in fact, some open-source projects (e.g., &lt;a href="https://github.com/forem/forem/blob/main/Containerfile.base" rel="noopener noreferrer"&gt;Forem&lt;/a&gt;) use it. However, Microsoft’s &lt;code&gt;mcr.microsoft.com/vscode/devcontainers/&lt;/code&gt; images are specifically prepared for use in DevContainers, adding various development tools. &lt;a href="https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/common-debian.sh" rel="noopener noreferrer"&gt;Here&lt;/a&gt;, for example, you can see one of the scripts executed in the &lt;code&gt;mcr.microsoft.com/vscode/devcontainers/&lt;/code&gt; images.&lt;/p&gt;

&lt;p&gt;For this reason, I prefer to use the &lt;code&gt;mcr.microsoft.com/vscode/devcontainers&lt;/code&gt; images. It’s just a personal preference and doesn’t mean that using other images as DevContainers is wrong. You can use any Docker image as a base.&lt;/p&gt;

&lt;h4&gt;
  
  
  And why use &lt;code&gt;mcr.microsoft.com/vscode/devcontainers/base&lt;/code&gt; instead of &lt;code&gt;mcr.microsoft.com/vscode/devcontainers/ruby&lt;/code&gt;?
&lt;/h4&gt;

&lt;p&gt;This is another decision made based on preference. I recently used &lt;code&gt;mcr.microsoft.com/vscode/devcontainers/base&lt;/code&gt; in my projects and manually installed Ruby due to a small “issue” (which might be specific to me 😀) I found in &lt;code&gt;mcr.microsoft.com/vscode/devcontainers/ruby&lt;/code&gt;. The &lt;code&gt;mcr.microsoft.com/vscode/devcontainers/ruby&lt;/code&gt; image installs two version managers: &lt;a href="https://github.com/rbenv/rbenv" rel="noopener noreferrer"&gt;rbenv&lt;/a&gt; and &lt;a href="https://rvm.io/" rel="noopener noreferrer"&gt;rvm&lt;/a&gt;, which can cause some issues. One issue I noticed is when your project has a &lt;code&gt;.ruby-version&lt;/code&gt; file (as in &lt;a href="https://github.com/duduribeiro/devcontainer-rails-demo/blob/main/.ruby-version" rel="noopener noreferrer"&gt;our example&lt;/a&gt;, generated by Rails). The &lt;code&gt;.ruby-version&lt;/code&gt; file tells the version managers which Ruby version to install. However, what I noticed is that when you don’t have this file, the &lt;code&gt;mcr.microsoft.com/vscode/devcontainers/ruby&lt;/code&gt; image installs Ruby using &lt;code&gt;rbenv&lt;/code&gt;, and when you do have this file in the project, it installs Ruby using &lt;code&gt;rvm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And what’s the problem with installing it using &lt;code&gt;rvm&lt;/code&gt;? Technically, there’s no issue, but you may run into some cases like I did. I was using the VSCode extension for &lt;code&gt;ruby-lsp&lt;/code&gt;, and the extension’s version manager detection was set to &lt;code&gt;auto&lt;/code&gt; (&lt;a href="https://github.com/Shopify/vscode-ruby-lsp#ruby-version-managers)%7B:target=%E2%80%9D%5C_blank%E2%80%9D%7D" rel="noopener noreferrer"&gt;https://github.com/Shopify/vscode-ruby-lsp#ruby-version-managers){:target=”\_blank”}&lt;/a&gt;. The extension tried to detect the installed version using &lt;code&gt;rbenv&lt;/code&gt;, but my DevContainer was using &lt;code&gt;rvm&lt;/code&gt;. Check out these two issues, &lt;a href="https://github.com/devcontainers/images/issues/572" rel="noopener noreferrer"&gt;https://github.com/devcontainers/images/issues/572&lt;/a&gt; and &lt;a href="https://github.com/microsoft/vscode-dev-containers/issues/704" rel="noopener noreferrer"&gt;https://github.com/microsoft/vscode-dev-containers/issues/704&lt;/a&gt;, for more details.&lt;/p&gt;

&lt;p&gt;Also, it’s not necessary to have a version manager in a container. When you need to use a different Ruby version, you can install it directly in the container, avoiding the installation of multiple versions together.&lt;/p&gt;

&lt;p&gt;Because of this difference, I preferred to use the &lt;code&gt;base&lt;/code&gt; image and manually install &lt;code&gt;Ruby&lt;/code&gt; with the following instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install rbenv and ruby
USER vscode

ARG RUBY_VERSION="3.2.2"
RUN git clone https://github.com/rbenv/rbenv.git /home/vscode/.rbenv \
    &amp;amp;&amp;amp; echo '[-f "/home/vscode/.rbenv/bin/rbenv"] &amp;amp;&amp;amp; eval "$(rbenv init - bash)" # rbenv' &amp;gt;&amp;gt; /home/vscode/.zshrc \
    &amp;amp;&amp;amp; echo '[-f "/home/vscode/.rbenv/bin/rbenv"] &amp;amp;&amp;amp; eval "$(rbenv init - bash)" # rbenv' &amp;gt;&amp;gt; /home/vscode/.bashrc \
    &amp;amp;&amp;amp; echo 'export PATH="/home/vscode/.rbenv/bin:$PATH"' &amp;gt;&amp;gt; /home/vscode/.zshrc \
    &amp;amp;&amp;amp; echo 'export PATH="/home/vscode/.rbenv/bin:$PATH"' &amp;gt;&amp;gt; /home/vscode/.bashrc \
    &amp;amp;&amp;amp; mkdir -p /home/vscode/.rbenv/versions \
    &amp;amp;&amp;amp; mkdir -p /home/vscode/.rbenv/plugins \
    &amp;amp;&amp;amp; git clone https://github.com/rbenv/ruby-build.git /home/vscode/.rbenv/plugins/ruby-build

ENV PATH "/home/vscode/.rbenv/bin/:/home/vscode/.rbenv/.rbenv/shims/:$PATH"

RUN rbenv install $RUBY_VERSION &amp;amp;&amp;amp; \
    rbenv global $RUBY_VERSION &amp;amp;&amp;amp; \
    rbenv versions

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last instruction in the &lt;code&gt;Dockerfile&lt;/code&gt; simply copies the file &lt;code&gt;.devcontainer/welcome.txt&lt;/code&gt; to the location &lt;code&gt;/usr/local/etc/vscode-dev-containers/first-run-notice.txt&lt;/code&gt; in the container. This file sets up a message that will be displayed when we open the terminal in VSCode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fvscode-welmcome-message.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fvscode-welmcome-message.png" alt="vscode welcome message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s create our &lt;code&gt;.devcontainer/welcome.txt&lt;/code&gt; then.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;👋 Welcome to "DemoApp"!

🛠️ Your environment is fully setup with all the required software.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our Dockerfile is ready. We can build the image to see if everything is okay by running &lt;code&gt;docker build&lt;/code&gt; in the root of the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% docker build -f .devcontainer/Dockerfile .

[+] Building 0.1s (10/10) FINISHED docker:orbstack
 =&amp;gt; [internal] load build definition from Dockerfile 0.0s
 =&amp;gt; =&amp;gt; transferring dockerfile: 1.47kB 0.0s
 =&amp;gt; [internal] load .dockerignore 0.0s
 =&amp;gt; =&amp;gt; transferring context: 766B 0.0s
 =&amp;gt; [internal] load metadata for mcr.microsoft.com/vscode/devcontainers/base:bullseye 0.0s
 =&amp;gt; [internal] load build context 0.0s
 =&amp;gt; =&amp;gt; transferring context: 265B 0.0s
 =&amp;gt; [1/5] FROM mcr.microsoft.com/vscode/devcontainers/base:bullseye 0.0s
 =&amp;gt; CACHED [2/5] RUN apt-get update &amp;amp;&amp;amp; apt-get -y install --no-install-recommends build-essential gnupg2 tar git zsh libssl-dev zlib1g-dev libyaml-dev curl libreadline-dev postgresql-client libpq 0.0s
 =&amp;gt; CACHED [3/5] RUN git clone https://github.com/rbenv/rbenv.git /home/vscode/.rbenv &amp;amp;&amp;amp; echo '[-f "/home/vscode/.rbenv/bin/rbenv"] &amp;amp;&amp;amp; eval "$(rbenv init - bash)" # rbenv' &amp;gt;&amp;gt; /home/vscode/.zshrc &amp;amp; 0.0s
 =&amp;gt; CACHED [4/5] RUN rbenv install 3.2.2 &amp;amp;&amp;amp; rbenv global 3.2.2 &amp;amp;&amp;amp; rbenv versions 0.0s
 =&amp;gt; [5/5] COPY .devcontainer/welcome.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt 0.0s
 =&amp;gt; exporting to image 0.0s
 =&amp;gt; =&amp;gt; exporting layers 0.0s
 =&amp;gt; =&amp;gt; writing image sha256:1ca67988b183ade50ca4f4a76e24d7cf76de0f7e7a12b0ea1d516fc25a67b501

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output indicates that our image is okay, and now we can proceed with building our &lt;code&gt;devcontainer.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The source code up to this point: &lt;a href="https://github.com/duduribeiro/devcontainer-rails-demo/releases/tag/1-dockerfile" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Specifying our development container in the &lt;code&gt;devcontainer.json&lt;/code&gt;.
&lt;/h3&gt;

&lt;p&gt;As we saw at the beginning, the &lt;code&gt;.devcontainer/devcontainer.json&lt;/code&gt; file contains the necessary configurations for our DevContainer so that &lt;a href="https://containers.dev/supporting" rel="noopener noreferrer"&gt;tools and services that support the devcontainer specification&lt;/a&gt; can start up and connect to the DevContainer. With this specification, we will be able to make VSCode set up our environment when it detects the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .devcontainer/devcontainer.json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby-rails-postgres
{
    "name": "DemoApp DevContainer",
    "build": {
        "dockerfile": "Dockerfile",
        "context": ".."
    },
    "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
    "remoteEnv": {
        "GIT_EDITOR": "code --wait"
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete specification and documentation for &lt;code&gt;devcontainer.json&lt;/code&gt; can be found &lt;a href="https://containers.dev/implementors/json_reference/" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but to summarize, here we define that our DevContainer is named “DemoApp DevContainer” and specify that it will use our Dockerfile created in the previous step. If we open our project with VSCode, we will receive a message indicating that it has detected a DevContainer specification and suggests opening the project inside the container:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fvscode-reopen-in-container.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fvscode-reopen-in-container.png" alt="vscode reopen in container option"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this popup, we can click on &lt;code&gt;Reopen in container&lt;/code&gt;, or in the command palette, you can search for the command directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fvscode-command-reopen-in-container.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fvscode-command-reopen-in-container.png" alt="vscode reopen in container command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo time!&lt;/p&gt;



&lt;p&gt;In the demo, we see that when we use the “Reopen in container” option, VSCode opens the project inside the container. Extensions run within the container and not on the local operating system. This allows extensions (e.g., LSP) to run their clients within the container itself. &lt;a href="https://code.visualstudio.com/docs/devcontainers/containers" rel="noopener noreferrer"&gt;Read here&lt;/a&gt; for more information on the DevContainers architecture. The image below illustrates this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Farchitecture-containers.png" alt="DevContainers Architecture"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;&lt;a href="https://code.visualstudio.com/docs/devcontainers/containers" rel="noopener noreferrer"&gt;https://code.visualstudio.com/docs/devcontainers/containers&lt;/a&gt;&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We also see that the tools are already installed in our container. We have &lt;code&gt;ruby&lt;/code&gt; and even &lt;code&gt;vim&lt;/code&gt; inside the container. And this is the difference from a production container. Here, we have everything we need for a complete development environment.&lt;/p&gt;

&lt;p&gt;Source code up to this point: &lt;a href="https://github.com/duduribeiro/devcontainer-rails-demo/releases/tag/2-basic-devcontainer" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Dependencies
&lt;/h3&gt;

&lt;p&gt;Inside our DevContainer, if we try to run the &lt;code&gt;bin/setup&lt;/code&gt; command of our project (which installs dependencies and sets up the database), we will encounter some errors.&lt;/p&gt;

&lt;p&gt;The first error we receive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/setup:8:in `system': No such file or directory - bun (Errno::ENOENT)
        from bin/setup:8:in `system!'
        from bin/setup:21:in `block in &amp;lt;main&amp;gt;'
        from /home/vscode/.rbenv/versions/3.2.2/lib/ruby/3.2.0/fileutils.rb:244:in `chdir'
        from /home/vscode/.rbenv/versions/3.2.2/lib/ruby/3.2.0/fileutils.rb:244:in `cd'
        from bin/setup:11:in `&amp;lt;main&amp;gt;'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdevcontainer-setup-error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdevcontainer-setup-error.png" alt="devcontainer setup error"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our Rails project was generated using &lt;a href="//bun.sh"&gt;bun&lt;/a&gt; as the JavaScript package manager and bundler. (Rails now supports Bun thanks to the great work of &lt;a href="https://twitter.com/jmeller" rel="noopener noreferrer"&gt;Jason Meller&lt;/a&gt; in this &lt;a href="https://github.com/rails/rails/pull/49241" rel="noopener noreferrer"&gt;PR&lt;/a&gt;). However, our DevContainer does not have &lt;code&gt;bun&lt;/code&gt; installed. We can fix this in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify our Dockerfile to install &lt;code&gt;bun&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use the &lt;a href="https://containers.dev/features" rel="noopener noreferrer"&gt;DevContainer Features&lt;/a&gt; &lt;code&gt;ghcr.io/shyim/devcontainers-features/bun&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will proceed with the second option. It’s a simple option for installing tools when they are available. We modify our &lt;code&gt;devcontainer.json&lt;/code&gt; to include the following instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    "features": {
        "ghcr.io/shyim/devcontainers-features/bun:0": {}
    },

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our &lt;code&gt;devcontainer.json&lt;/code&gt; looks like this now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby-rails-postgres
{
    "name": "DemoApp DevContainer",
    "build": {
        "dockerfile": "Dockerfile",
        "context": ".."
    },
    "features": {
        "ghcr.io/shyim/devcontainers-features/bun:0": {}
    },
    "workspaceFolder": "/workspace",
    "remoteEnv": {
        "GIT_EDITOR": "code --wait"
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VSCode notices that we have modified the specification of our DevContainer and suggests rebuilding it: &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Frebuild-container-option.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Frebuild-container-option.png" alt="rebuild container option"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After finishing the DevContainer build, if we run &lt;code&gt;bun -v&lt;/code&gt;, we will see that it is now installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vscode ➜ /workspace (main) $ bun -v
1.0.3

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;bin/setup&lt;/code&gt; again, and now the failure occurs when preparing the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PG::ConnectionBad: could not connect to server: No such file or directory
        Is the server running locally and accepting
        connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdevcontainer-pg-error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdevcontainer-pg-error.png" alt="postgres error on devcontainer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our project is configured to use PostgreSQL, and this error occurs because we don’t have it running in our container. Let’s run our database using &lt;a href="https://containers.dev/guide/dockerfile#docker-compose" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;, which allows us to define a development environment with multiple containers. Instead of adding PostgreSQL to our Dockerfile, we’ll add an additional container to our environment via Compose.&lt;/p&gt;

&lt;p&gt;Let’s create our &lt;code&gt;.devcontainer/docker-compose.yml&lt;/code&gt; file. Refer to the &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for more information on Docker Compose specification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'

services:
  app:
    build:
      context: ..
      dockerfile: .devcontainer/Dockerfile

    volumes:
      - ..:/workspace:cached
      - $HOME/.ssh/:/home/vscode/.ssh/
    depends_on:
      - postgres
    environment:
      - DATABASE_URL=postgres://postgres:postgres@postgres:5432

    # Overrides default command so things don't shut down after the process ends.
    command: sleep infinity

    # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
    network_mode: service:postgres

  postgres:
    image: postgres:15-alpine
    restart: unless-stopped
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: postgres
      POSTGRES_PASSWORD: postgres
    healthcheck:
      test: pg_isready -U postgres -h 127.0.0.1
      interval: 5s

volumes:
  postgres-data:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In summary, in our Compose file, we define two services: the &lt;code&gt;app&lt;/code&gt;, which will be our &lt;code&gt;devcontainer&lt;/code&gt; and is built based on our Dockerfile, and the &lt;code&gt;postgres&lt;/code&gt;, which will use the official &lt;code&gt;postgres&lt;/code&gt; image. We add an environment variable &lt;code&gt;DATABASE_URL&lt;/code&gt; with the value &lt;code&gt;postgres://postgres:postgres@postgres:5432&lt;/code&gt; to inform our project about this database endpoint. In our &lt;code&gt;devcontainer&lt;/code&gt;, we override the default command with &lt;code&gt;command: sleep infinity&lt;/code&gt; to prevent the container from exiting when the main process finishes.&lt;/p&gt;

&lt;p&gt;We also modify the &lt;code&gt;.devcontainer/devcontainer.json&lt;/code&gt; file to use Docker Compose instead of just our Dockerfile. We remove the &lt;code&gt;build&lt;/code&gt; directive and add the &lt;code&gt;dockerComposeFile&lt;/code&gt; and &lt;code&gt;service&lt;/code&gt; sections. Here’s the resulting file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .devcontainer/devcontainer.json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby-rails-postgres
{
    "name": "DemoApp DevContainer",
    "dockerComposeFile": "docker-compose.yml",
    "service": "app",
    "features": {
        "ghcr.io/shyim/devcontainers-features/bun:0": {}
    },
    "workspaceFolder": "/workspace",
    "remoteEnv": {
        "GIT_EDITOR": "code --wait"
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After rebuilding the DevContainer and accessing it, we run &lt;code&gt;bin/setup&lt;/code&gt; again, and now the database is created, and our setup is completed successfully.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdevcontainer-setup-finished.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdevcontainer-setup-finished.png" alt="devcontainer setup finished"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Project code up to this point: &lt;a href="https://github.com/duduribeiro/devcontainer-rails-demo/releases/tag/4-use-compose" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the project
&lt;/h3&gt;

&lt;p&gt;With the project set up, we can start the project with the command &lt;code&gt;bin/dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Frunning-the-project.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Frunning-the-project.png" alt="running the project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application will start inside the container, and VSCode will give us the option to open the browser. When we access at &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; it show the page with success:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdevcontainer-rails-page.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fdevcontainer-rails-page.png" alt="page opened with success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything is set up, and our project is running, achieving our goal. We have a development environment that anyone who clones the project can run in just a few minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some improvements to our DevContainer
&lt;/h2&gt;

&lt;p&gt;Let’s add a few more things to our DevContainer to improve our experience.&lt;/p&gt;

&lt;p&gt;The first change we’ll make is to add an &lt;code&gt;onCreateCommand&lt;/code&gt;. This directive tells the DevContainer what command to run when it’s created. Cloud DevContainer services (like GitHub Codespaces) also use this command for caching and prebuilding the container to reduce setup time. In our &lt;code&gt;onCreateCommand&lt;/code&gt; script, we’ll update system gems (like Bundler) and run &lt;code&gt;bin/setup&lt;/code&gt;. This way, every time a DevContainer is created, we won’t need to run &lt;code&gt;bin/setup&lt;/code&gt; and can directly start the server when we initiate the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .devcontainer/devcontainer.json

 "onCreateCommand": ".devcontainer/onCreateCommand.sh"


#!/usr/bin/env bash

# .devcontainer/onCreateCommand.sh

echo "Updating RubyGems..."
gem update --system -N

echo "Setup.."
bin/setup

echo "Seeding database..."
bin/rails db:seed

echo "Done!"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another improvement we can make is to install extensions and add VSCode settings by default. This way, everyone who starts a DevContainer will have the project’s standard extensions installed. In the example below, we have some extensions like &lt;code&gt;ruby-lsp&lt;/code&gt; and &lt;code&gt;sqltools&lt;/code&gt; for connecting to the database. We also adjust the &lt;code&gt;sqltools&lt;/code&gt; settings to include the database connection information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .devcontainer/devcontainer.json

    "customizations": {
        "vscode": {
            // Set *default* container specific settings.json values on container create.
            "settings": {
                "sqltools.connections": [
                    {
                        "name": "Development Database",
                        "driver": "PostgreSQL",
                        "previewLimit": 50,
                        "server": "postgres",
                        "port": 5432,
                        "database": "devcontainer_rails_demo_development",
                        "username": "postgres",
                        "password": "postgres"
                    },
                    {
                        "name": "Test Database",
                        "driver": "PostgreSQL",
                        "previewLimit": 50,
                        "server": "postgres",
                        "port": 5432,
                        "database": "devcontainer_rails_demo_test",
                        "username": "postgres",
                        "password": "postgres"
                    }
                ],
                "editor.formatOnSave": true
            },
            "extensions": [
                "Shopify.ruby-lsp",
                "manuelpuyol.erb-linter",
                "GitHub.github-vscode-theme",
                "eamodio.gitlens",
                "aki77.rails-db-schema",
                "bung87.rails",
                "mtxr.sqltools-driver-pg",
                "mtxr.sqltools",
                "testdouble.vscode-standard-ruby"
            ],
            "rubyLsp.enableExperimentalFeatures": true
        }
    },

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also fix the ports that are accessible from the container. When we start the server, VSCode identifies that port 3000 is open and performs an auto-forward. However, we can pre-establish this in the &lt;code&gt;devcontainer.json&lt;/code&gt; and even open the database port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // .devcontainer/devcontainer.json

    // Use 'forwardPorts' to make a list of ports inside the container available locally.
    "forwardPorts": [
        3000,
        5432,
    ],
    "portsAttributes": {
        "3000": {
            "label": "web",
            "onAutoForward": "notify",
            "requireLocalPort": true
        },
        "5432": {
            "label": "postgres"
        }      
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these final improvements, our DevContainer is now ready for use. The final version up and running:&lt;/p&gt;



&lt;p&gt;And with that, we’ve achieved the goal: using containers to reduce friction in setting up your development environment. The final source code of the project is available at &lt;a href="https://github.com/duduribeiro/devcontainer-rails-demo/" rel="noopener noreferrer"&gt;https://github.com/duduribeiro/devcontainer-rails-demo/&lt;/a&gt;. You can clone it and set up the environment in just a few minutes to try it out for yourself.&lt;/p&gt;




&lt;p&gt;Bonus:&lt;/p&gt;

&lt;p&gt;Now that you have your development environment setup with DevContainers, you can also easily use services like &lt;a href="https://github.com/features/codespaces" rel="noopener noreferrer"&gt;GitHub Codespaces&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Even this blog post was &lt;a href="https://github.com/duduribeiro/blog/commit/a0740c12fb9f95ce6a9aecea2e087911658e9384" rel="noopener noreferrer"&gt;written inside a DevContainer&lt;/a&gt;. I needed to install Ruby 2.6.8 and some dependencies failed to intall on my machine. So I decided to run this on a DevContainer to have a easy way to run the project every time I want to write a new post.&lt;/p&gt;




&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers" rel="noopener noreferrer"&gt;https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/palkan/railsconf-2019-terraforming-legacy-rails-applications" rel="noopener noreferrer"&gt;https://speakerdeck.com/palkan/railsconf-2019-terraforming-legacy-rails-applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/2015-06-30-scripts-to-rule-them-all/" rel="noopener noreferrer"&gt;https://github.blog/2015-06-30-scripts-to-rule-them-all/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/2021-08-11-githubs-engineering-team-moved-codespaces/" rel="noopener noreferrer"&gt;https://github.blog/2021-08-11-githubs-engineering-team-moved-codespaces/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://containers.dev/" rel="noopener noreferrer"&gt;https://containers.dev/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/docs/devcontainers/containers" rel="noopener noreferrer"&gt;https://code.visualstudio.com/docs/devcontainers/containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://12factor.net/dev-prod-parity" rel="noopener noreferrer"&gt;https://12factor.net/dev-prod-parity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/forem/forem/tree/main/.devcontainer" rel="noopener noreferrer"&gt;https://github.com/forem/forem/tree/main/.devcontainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/robzolkos/rails-devcontainer/" rel="noopener noreferrer"&gt;https://github.com/robzolkos/rails-devcontainer/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/vscode-dev-containers/issues/704" rel="noopener noreferrer"&gt;https://github.com/microsoft/vscode-dev-containers/issues/704&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks ☕️&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devcontainers</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Creating a Docker image with a preloaded database</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Fri, 29 Jan 2021 15:00:00 +0000</pubDate>
      <link>https://dev.to/caduribeiro/creating-a-docker-image-with-a-preloaded-database-3dom</link>
      <guid>https://dev.to/caduribeiro/creating-a-docker-image-with-a-preloaded-database-3dom</guid>
      <description>&lt;p&gt;Imagine that we have the following Postgresql database dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- PostgreSQL database dump&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="c1"&gt;-- Dumped from database version 11.5&lt;/span&gt;
&lt;span class="c1"&gt;-- Dumped by pg_dump version 11.3&lt;/span&gt;

&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;statement_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;lock_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;idle_in_transaction_session_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;client_encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;standard_conforming_strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'search_path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;check_function_bodies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;xmloption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;client_min_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;row_security&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Name: my_db; Type: DATABASE; Schema: -; Owner: postgres&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;my_db&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;TEMPLATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template0&lt;/span&gt; &lt;span class="k"&gt;ENCODING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'UTF8'&lt;/span&gt; &lt;span class="n"&gt;LC_COLLATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'en_US.utf8'&lt;/span&gt; &lt;span class="n"&gt;LC_CTYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'en_US.utf8'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;my_db&lt;/span&gt; &lt;span class="k"&gt;OWNER&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;connect&lt;/span&gt; &lt;span class="n"&gt;my_db&lt;/span&gt;

&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;statement_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;lock_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;idle_in_transaction_session_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;client_encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;standard_conforming_strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'search_path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;check_function_bodies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;xmloption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;client_min_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;row_security&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;default_tablespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;default_with_oids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Name: clients; Type: TABLE; Schema: public; Owner: postgres&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="k"&gt;OWNER&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Name: clients_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients_id_seq&lt;/span&gt;
    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt;
    &lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;NO&lt;/span&gt; &lt;span class="k"&gt;MINVALUE&lt;/span&gt;
    &lt;span class="k"&gt;NO&lt;/span&gt; &lt;span class="k"&gt;MAXVALUE&lt;/span&gt;
    &lt;span class="k"&gt;CACHE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients_id_seq&lt;/span&gt; &lt;span class="k"&gt;OWNER&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Name: clients_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients_id_seq&lt;/span&gt; &lt;span class="n"&gt;OWNED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Name: clients id; Type: DEFAULT; Schema: public; Owner: postgres&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'public.clients_id_seq'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Data for Name: clients; Type: TABLE DATA; Schema: public; Owner: postgres&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Name: clients_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'public.clients_id_seq'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Name: clients clients_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;
    &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;clients_pkey&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- PostgreSQL database dump complete&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a simple database with a &lt;code&gt;Clients&lt;/code&gt; table and 2 records.&lt;/p&gt;

&lt;p&gt;If we want to start a Postgresql Docker container with this dump loaded to share with our team, we can add this SQL file into the &lt;em&gt;/docker-entrypoint-initdb.d/&lt;/em&gt; folder inside the container, like &lt;a href="https://hub.docker.com/_/postgres" rel="noopener noreferrer"&gt;explained into the Postgresql Image docs from DockerHub&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Initialization scripts If you would like to do additional initialization in an image derived from this one, add one or more *.sql, *.sql.gz, or *.sh scripts under /docker-entrypoint-initdb.d (creating the directory if necessary). After the entrypoint calls initdb to create the default postgres user and database, it will run any *.sql files, run any executable *.sh scripts, and source any non-executable *.sh scripts found in that directory to do further initialization before starting the service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following Dockerfile uses &lt;em&gt;postgres:11-alpine&lt;/em&gt; as base image and copies &lt;em&gt;test_dump.sql&lt;/em&gt; file to the entrypoint folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; postgres:11-alpine&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; test_dump.sql /docker-entrypoint-initdb.d/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we build this image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; preloaded_db:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and start a container with the generated image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker container run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;--name&lt;/span&gt; test_preloaded_db preloaded_db:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can see in our database that the database was created. (password is &lt;code&gt;postgres&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;psql &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-U&lt;/span&gt; postgres
&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# \c my_db&lt;/span&gt;
psql &lt;span class="o"&gt;(&lt;/span&gt;11.3, server 11.5&lt;span class="o"&gt;)&lt;/span&gt;
You are now connected to database “my_db” as user “postgres”.
&lt;span class="nv"&gt;my_db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# SELECT * FROM clients;&lt;/span&gt;
 &lt;span class="nb"&gt;id&lt;/span&gt; | name
 — — + — — — — —
 1 | Client 1
 2 | Client 2
&lt;span class="o"&gt;(&lt;/span&gt;2 rows&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome. Now we have a docker image that has our database loaded. But if we check the log of this container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker container logs test_preloaded_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can see CREATE DATABASE and CREATE TABLE commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/test_dump.sql
SET
SET
SET
SET
SET
 set_config
&lt;span class="nt"&gt;------------&lt;/span&gt;

&lt;span class="o"&gt;(&lt;/span&gt;1 row&lt;span class="o"&gt;)&lt;/span&gt;

SET
SET
SET
SET
CREATE DATABASE
ALTER DATABASE
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tell us that the dump is being processed every time we create the container. If we destroy this container and create a new one, the dump will be processed again. This works fine but if we have a big database with a big dump file, the startup process of the container will be slow because it can take some time to process the whole dump. We can fix it by keeping the database preloaded in the image.&lt;/p&gt;

&lt;p&gt;Before we moving on, let’s destroy the container we created&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker container &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; test_preloaded_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Preloading the database in the image
&lt;/h2&gt;

&lt;p&gt;To preload the database in the image, we need to tell our Dockerfile to execute the same &lt;code&gt;entrypoint&lt;/code&gt; of the original PostgreSQL image so it can execute the dump in the build step. Let’s use &lt;a href="https://docs.docker.com/develop/develop-images/multistage-build" rel="noopener noreferrer"&gt;Multi-Stage build&lt;/a&gt; to divide our build in two steps. The first one will execute the &lt;code&gt;entrypoint&lt;/code&gt; with the dump file and the second one will copy the data folder to the resulting image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# dump build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres:11-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dumper&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; test_dump.sql /docker-entrypoint-initdb.d/&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sed"&lt;/span&gt;, &lt;span class="s2"&gt;"-i"&lt;/span&gt;, &lt;span class="s2"&gt;"s/exec &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;/echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;skipping...&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;, &lt;span class="s2"&gt;"/usr/local/bin/docker-entrypoint.sh"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; POSTGRES_USER=postgres&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; POSTGRES_PASSWORD=postgres&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PGDATA=/data&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bin/docker-entrypoint.sh"&lt;/span&gt;, &lt;span class="s2"&gt;"postgres"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# final build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; postgres:11-alpine&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=dumper /data $PGDATA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the first step, we have the following instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FROM postgres:11-alpine as dumper&lt;/strong&gt; We define the base image our step will use. &lt;code&gt;postgres&lt;/code&gt; with the &lt;code&gt;11-alpine&lt;/code&gt; tag in this case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COPY test_dump.sql /docker-entrypoint-initdb.d/&lt;/strong&gt; Copy the &lt;code&gt;test_dump.sql&lt;/code&gt; file to the &lt;code&gt;/docker-entrypoint-initdb.d/&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RUN [“sed”, “-i”, “s/exec "$@"/echo "skipping…"/”, “/usr/local/bin/docker-entrypoint.sh”]&lt;/strong&gt; We need to execute this &lt;code&gt;sed&lt;/code&gt; command in order to remove the &lt;code&gt;exec "$@"&lt;/code&gt; content that exists in the &lt;code&gt;docker-entrypoint.sh&lt;/code&gt; file so it will not start the PostgreSQL daemon (we don’t need it on this step).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ENV POSTGRES_USER=postgres; ENV POSTGRES_PASSWORD=postgres; ENV PGDATA=/data&lt;/strong&gt; Sets environment variables to define &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; and tell PostgreSQL to use &lt;code&gt;/data&lt;/code&gt; as data folder, so we can copy it in the next step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RUN [“/usr/local/bin/docker-entrypoint.sh”, “postgres”]&lt;/strong&gt; Execute the entrypoint itself. It will execute the dump and load the data into &lt;code&gt;/data&lt;/code&gt; folder. Since we executed the &lt;code&gt;sed&lt;/code&gt; command to remove the &lt;code&gt;$@&lt;/code&gt; content it will not run the PostgreSQL daemon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second step contains only this instruction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;COPY — from=dumper /data $PGDATA&lt;/strong&gt; This will copy all files from &lt;code&gt;/data&lt;/code&gt; folder from the &lt;code&gt;dumper&lt;/code&gt; step into the $PGDATA from this current step, making our data preloaded when we start the container (without needing to run the dump every time we create a new container).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we build this Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; preloaded_db:new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see in the output the dump being processed and after everything is finished, the image is built.&lt;/p&gt;

&lt;p&gt;and we can start the container with this new image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker container run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;--name&lt;/span&gt; test_preloaded_db preloaded_db:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and our database is loaded&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;psql &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-U&lt;/span&gt; postgres
psql &lt;span class="o"&gt;(&lt;/span&gt;11.3, server 11.5&lt;span class="o"&gt;)&lt;/span&gt;
Type “help” &lt;span class="k"&gt;for &lt;/span&gt;help.
&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# \c my_db&lt;/span&gt;
psql &lt;span class="o"&gt;(&lt;/span&gt;11.3, server 11.5&lt;span class="o"&gt;)&lt;/span&gt;
You are now connected to database “my_db” as user “postgres”.
&lt;span class="nv"&gt;my_db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# SELECT * FROM clients;&lt;/span&gt;
 &lt;span class="nb"&gt;id&lt;/span&gt; | name
 — — + — — — — —
 1 | Client 1
 2 | Client 2
&lt;span class="o"&gt;(&lt;/span&gt;2 rows&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if we check the logs now, the dump is not being processed every time we create the container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker container logs test_preloaded_db
2019–09–16 01:42:22.458 UTC [1] LOG: listening on IPv4 address “0.0.0.0”, port 5432
2019–09–16 01:42:22.458 UTC [1] LOG: listening on IPv6 address “::”, port 5432
2019–09–16 01:42:22.460 UTC [1] LOG: listening on Unix socket “/var/run/postgresql/.s.PGSQL.5432”
2019–09–16 01:42:22.470 UTC [18] LOG: database system was shut down at 2019–09–16 01:41:02 UTC
2019–09–16 01:42:22.473 UTC [1] LOG: database system is ready to accept connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that only the PostgreSQL startup is being done. No dump is being executed because it was executed in the &lt;code&gt;build&lt;/code&gt; image step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Makefile to make the process easier
&lt;/h2&gt;

&lt;p&gt;I like to create a Makefile to make easier the process of making a database dump and creating an image. This Makefile will contain commands to create the dump the database, create an image and tag it by date allowing me to have daily dumps on my registry to download.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;default all fetch_dump&lt;/span&gt;

&lt;span class="nv"&gt;date&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nv"&gt;TARGET_IMAGE&lt;/span&gt; &lt;span class="o"&gt;?=&lt;/span&gt; my_app

&lt;span class="nl"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_vars fetch_dump generate_image push_to_registry clean finished&lt;/span&gt;

&lt;span class="nl"&gt;check_vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;DB_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"You need to set DB_ENDPOINT environment variable"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;DB_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"You need to set DB_NAME environment variable"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;DESTINATION_REPOSITORY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"You need to set DESTINATION_REPOSITORY environment variable"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;fetch_dump&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;DB_USER ?= postgres&lt;/span&gt;
&lt;span class="nl"&gt;fetch_dump&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"====== Fetching remote dump ======"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; pg_dump &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;DB_ENDPOINT&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;DB_NAME&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;DB_USER&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dump.sql

&lt;span class="nl"&gt;generate_image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="nl"&gt;generate_image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;TARGET_IMAGE&lt;span class="p"&gt;)&lt;/span&gt;:latest &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;DESTINATION_REPOSITORY&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;TARGET_IMAGE&lt;span class="p"&gt;)&lt;/span&gt;:latest &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;DESTINATION_REPOSITORY&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;TARGET_IMAGE&lt;span class="p"&gt;)&lt;/span&gt;:&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;push_to_registry&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"====== Pushing image to repository ======"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker push &lt;span class="p"&gt;$(&lt;/span&gt;DESTINATION_REPOSITORY&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;TARGET_IMAGE&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"====== Cleaning used files ======"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; dump.sql

&lt;span class="nl"&gt;finished&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Finished with success. Pushed image to &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;DESTINATION_REPOSITORY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;TARGET_IMAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I can execute the following command to generate my image with a new dump&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nv"&gt;DB_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 &lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_db &lt;span class="nv"&gt;TARGET_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp-data &lt;span class="nv"&gt;DESTINATION_REPOSITORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gcr.io/my_project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command usually is integrated in a Cron job in some server to be executed daily. With this I can have on my image registry dumps from each day.&lt;/p&gt;

&lt;p&gt;Another interesting thing to do is to add some SQL script to obfuscate users data. &lt;a href="https://blog.taadeem.net/english/2018/10/29/Introducing-PostgreSQL-Anonymizer" rel="noopener noreferrer"&gt;This article can be helpful if you want to achive this&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fthats_all.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcadu.dev%2Fassets%2Fimages%2Fthats_all.png" alt="image tooltip here"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks ☕️&lt;/p&gt;

</description>
      <category>docker</category>
      <category>database</category>
    </item>
    <item>
      <title>Reduce your Docker images (an example with Ruby)</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Tue, 26 Feb 2019 15:14:06 +0000</pubDate>
      <link>https://dev.to/caduribeiro/reduce-your-docker-images-an-example-with-ruby-30db</link>
      <guid>https://dev.to/caduribeiro/reduce-your-docker-images-an-example-with-ruby-30db</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vqAHuH1q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/0%2AoZtQbUoanv_gIHLX.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vqAHuH1q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/0%2AoZtQbUoanv_gIHLX.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A big problem that we face when deploying Docker into production is the image size. Large images take longer to download, consume much of your cloud network traffic quota, cost more money to be stored on the repository and don’t bring any good value.&lt;/p&gt;

&lt;p&gt;In most situations, when we create a Docker image, we add steps and dependencies that sometimes we don’t need in the final image that will run in production.&lt;/p&gt;

&lt;p&gt;I will use the following application as an example:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/opensanca/opensanca%5C_jobs"&gt;https://github.com/opensanca/opensanca_jobs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the Dockerfile that generates our image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM ruby:2.5.0-alpine
LABEL maintainer="[contato@opensanca.com.br](mailto:contato@opensanca.com.br)"
ARG rails\_env="development"
ARG build\_without=""
ENV SECRET\_KEY\_BASE=dumb
RUN apk update \
&amp;amp;&amp;amp; apk add \
openssl \
tar \
build-base \
tzdata \
postgresql-dev \
postgresql-client \
nodejs \
&amp;amp;&amp;amp; wget [https://yarnpkg.com/latest.tar.gz](https://yarnpkg.com/latest.tar.gz) \
&amp;amp;&amp;amp; mkdir -p /opt/yarn \
&amp;amp;&amp;amp; tar -xf latest.tar.gz -C /opt/yarn --strip 1 \
&amp;amp;&amp;amp; mkdir -p /var/app
ENV PATH="$PATH:/opt/yarn/bin" BUNDLE\_PATH="/gems" BUNDLE\_JOBS=2 RAILS\_ENV=${rails\_env} BUNDLE\_WITHOUT=${bundle\_without}
COPY . /var/app
WORKDIR /var/app
RUN bundle install &amp;amp;&amp;amp; yarn &amp;amp;&amp;amp; bundle exec rake assets:precompile
CMD rails s -b 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the command used to build it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t openjobs:latest --build-arg build\_without="development test" --build-arg rails\_env="production" .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wng8DfOF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2ARujRaeSrBXgUJHVNWjQMIg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wng8DfOF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2ARujRaeSrBXgUJHVNWjQMIg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This build generated an image with almost 1GB!!! 😱.&lt;/p&gt;

&lt;p&gt;This image has some unnecessary stuff, like node but yarn (we only need them to precompile the assets but not to execute the application itself).&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Stage build
&lt;/h3&gt;

&lt;p&gt;Docker introduced the concept of &lt;a href="https://docs.docker.com/develop/develop-images/multistage-build/"&gt;Multi-Stage build&lt;/a&gt; in version 17.05. This build technic allows us to split our Dockerfile into several statements FROM. Each statement can use a different base image and you can copy artifacts from one stage to another, without bringing stuff that you don’t want in the final image. Our final image will only contain the build wrote in the last stage.&lt;/p&gt;

&lt;p&gt;Now we have a Dockerfile divided into two stages. Pre-build and Final-Build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pre-build stage
FROM ruby:2.5.0-alpine AS pre-builder
ARG rails\_env="development"
ARG build\_without=""
ENV SECRET\_KEY\_BASE=dumb
RUN apk add --update --no-cache \
openssl \
tar \
build-base \
tzdata \
postgresql-dev \
postgresql-client \
nodejs \
&amp;amp;&amp;amp; wget [https://yarnpkg.com/latest.tar.gz](https://yarnpkg.com/latest.tar.gz) \
&amp;amp;&amp;amp; mkdir -p /opt/yarn \
&amp;amp;&amp;amp; tar -xf latest.tar.gz -C /opt/yarn --strip 1 \
&amp;amp;&amp;amp; mkdir -p /var/app
ENV PATH="$PATH:/opt/yarn/bin" BUNDLE\_PATH="/gems" BUNDLE\_JOBS=2 RAILS\_ENV=${rails\_env} BUNDLE\_WITHOUT=${bundle\_without}
COPY . /var/app
WORKDIR /var/app
RUN bundle install &amp;amp;&amp;amp; yarn &amp;amp;&amp;amp; bundle exec rake assets:precompile

# final build stage
FROM ruby:2.5.0-alpine
LABEL maintainer="[contato@opensanca.com.br](mailto:contato@opensanca.com.br)"
RUN apk add --update --no-cache \
openssl \
tzdata \
postgresql-dev \
postgresql-client
COPY --from=pre-builder /gems/ /gems/
COPY --from=pre-builder /var/app /var/app
ENV RAILS\_LOG\_TO\_STDOUT true
WORKDIR /var/app
EXPOSE 3000
CMD rails s -b 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the pre-build stage we install node and yarn, all dependencies and precompile the assets. In the final stage, we use an alpine image (which is very small) with ruby, we install only the necessary dependencies to run the application and we then copy the libraries and assets generated in the build-stage with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COPY --from=pre-builder /gems/ /gems/
COPY --from=pre-builder /var/app /var/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing the build with this Dockerfile, we have now a 562MB image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yQE3sqc2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AG7h0VTW1JD9tKZ7DHnqM9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yQE3sqc2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AG7h0VTW1JD9tKZ7DHnqM9w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have already reduced almost half the image size, but can we reduce it further?? 🤔&lt;/p&gt;

&lt;p&gt;Yes. We can do some actions to reduce more this image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing unnecessary files
&lt;/h3&gt;

&lt;p&gt;We can delete files that are not necessary from the image, like cache and temporary files used by the installed libraries. We can add a .dockerignore file, telling the build what not to send to the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# build stage
FROM ruby:2.5.0-alpine AS pre-builder
ARG rails\_env="development"
ARG build\_without=""
ENV SECRET\_KEY\_BASE=dumb
RUN apk add --update --no-cache \
openssl \
tar \
build-base \
tzdata \
postgresql-dev \
postgresql-client \
nodejs \
&amp;amp;&amp;amp; wget [https://yarnpkg.com/latest.tar.gz](https://yarnpkg.com/latest.tar.gz) \
&amp;amp;&amp;amp; mkdir -p /opt/yarn \
&amp;amp;&amp;amp; tar -xf latest.tar.gz -C /opt/yarn --strip 1 \
&amp;amp;&amp;amp; mkdir -p /var/app
ENV PATH="$PATH:/opt/yarn/bin" BUNDLE\_PATH="/gems" BUNDLE\_JOBS=4 RAILS\_ENV=${rails\_env} BUNDLE\_WITHOUT=${bundle\_without}
COPY . /var/app
WORKDIR /var/app
RUN bundle install &amp;amp;&amp;amp; yarn &amp;amp;&amp;amp; bundle exec rake assets:precompile \
&amp;amp;&amp;amp; rm -rf /gems/cache/\*.gem \
&amp;amp;&amp;amp; find /gems/gems/ -name "\*.c" -delete \
&amp;amp;&amp;amp; find /gems/gems/ -name "\*.o" -delete

# final stage
FROM ruby:2.5.0-alpine
LABEL maintainer="[contato@opensanca.com.br](mailto:contato@opensanca.com.br)"
RUN apk add --update --no-cache \
openssl \
tzdata \
postgresql-dev \
postgresql-client
COPY --from=pre-builder /gems/ /gems/
COPY --from=pre-builder /var/app /var/app
ENV RAILS\_LOG\_TO\_STDOUT true
WORKDIR /var/app
EXPOSE 3000
CMD rails s -b 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this new Dockerfile, we added this part that removes caches and temporary C files used to build the libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;&amp;amp; rm -rf /gems/cache/\*.gem \
&amp;amp;&amp;amp; find /gems/gems/ -name "\*.c" -delete \
&amp;amp;&amp;amp; find /gems/gems/ -name "\*.o" -delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also included our .dockerignore to tell the build process the files that we don’t want in the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env\*
.git
.gitignore
.codeclimate.yml
.dockerignore
.gitlab-ci.yml
.hound.yml
.travis.yml
LICENSE.md
README.md
docker-compose.\*
Dockerfile
log/\*
node\_modules/\*
public/assets/\*
storage/\*
public/packs/\*
public/packs-test/\*
tmp/\*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these two steps, now our image has 272MB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EfdqRrYi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AeZJTGWQQHJTdqKyvfyvFjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EfdqRrYi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AeZJTGWQQHJTdqKyvfyvFjw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can reduce it even more. For production, we don’t need test folders, npm raw folder (they are already included on the asset pipeline), no precompiled assets and caches.&lt;/p&gt;

&lt;p&gt;To remove this files, we can include a strategy of passing an argument to build (we will call it: to_remove)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
ARG to\_remove
...
RUN bundle install &amp;amp;&amp;amp; yarn &amp;amp;&amp;amp; bundle exec rake assets:precompile \
&amp;amp;&amp;amp; rm -rf /usr/local/bundle/cache/\*.gem \
 &amp;amp;&amp;amp; find /usr/local/bundle/gems/ -name "\*.c" -delete \
 &amp;amp;&amp;amp; find /usr/local/bundle/gems/ -name "\*.o" -delete \
 &amp;amp;&amp;amp; rm -rf $to\_remove # Here we remove all files that we passed as an argument to the build.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this argument, we will pass all the files that we don’t want in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t openjobs:reduced --build-arg build\_without="development test" --build-arg rails\_env="production" . --build-arg to\_remove="spec node\_modules app/assets vendor/assets lib/assets tmp/cache"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;— build-arg to_remove=”spec node_modules app/assets vendor/assets lib/assets tmp/cache”&lt;/code&gt;. These are the folders that we want to remove from our build process. We don’t need them to run in production.&lt;/p&gt;

&lt;p&gt;Removing these files, now we have an image with 164MB, almost 6 times smaller than the original one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HosoG7uS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AdOtqxq0ssllOzV6v_iVjmw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HosoG7uS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AdOtqxq0ssllOzV6v_iVjmw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---S7B8xJT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2ALt65Wab0jeBX6OSkgUFcAQ.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---S7B8xJT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2ALt65Wab0jeBX6OSkgUFcAQ.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you still don’t believe me and want to see it, this is the PR that generates this reduction: &lt;a href="https://github.com/opensanca/opensanca_jobs/pull/164"&gt;https://github.com/opensanca/opensanca_jobs/pull/164&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ft3yupCX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AeqsPaN0ft0DkhHczXD5vJA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ft3yupCX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AeqsPaN0ft0DkhHczXD5vJA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cheers 🍻&lt;/p&gt;




</description>
      <category>docker</category>
    </item>
    <item>
      <title>An Introduction into Spacemacs from a Vim user</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Fri, 23 Nov 2018 12:10:41 +0000</pubDate>
      <link>https://dev.to/caduribeiro/an-introduction-into-spacemacs-from-a-vim-user-118</link>
      <guid>https://dev.to/caduribeiro/an-introduction-into-spacemacs-from-a-vim-user-118</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rjqljng22ai1fv324ox.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rjqljng22ai1fv324ox.jpg" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m a Vim user. I use it in my workflow all the time, but I enjoy trying other editors sometimes and I will share my experience playing with Spacemacs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Spacemacs?
&lt;/h3&gt;

&lt;p&gt;Spacemacs is a starter kit for Emacs that makes it easier for anyone to use it. It has the &lt;a href="https://github.com/emacs-evil/evil" rel="noopener noreferrer"&gt;Evil&lt;/a&gt; plugin by default. Evil is a layer for Emacs that emulates Vim. I always try vim mode in all other editors and they all are not so good, Evil is one of the best Vim emulators that I’ve already seen. It makes it easier for me to use Emacs, since it emulates Vim commands that I already know.&lt;/p&gt;

&lt;p&gt;Spacemacs has easy to remember keybindings. Everything starts with the SPC key (it is like the leader key from Vim) and all belongs to some group. For example: SPC b for all buffers commands and SPC p for all project commands. If you don’t remember the command, press SPC SPC and it brings an autocomplete buffer to search for commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I experiment another editor?
&lt;/h3&gt;

&lt;p&gt;It’s hard for me to learn all the stuffs from a tool in a day or two. So when I want to learn more about an editor, I force myself to experiment it for at least one week, trying to do everything on the new editor without opening others. To use an editor I need to learn how to search files, how to navigate through files and how split the window, only this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;First, you should install Emacs.&lt;br&gt;&lt;br&gt;
You can use Homebrew to install emacs on mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install emacs --use-git-head --cocoa --srgb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install on Linux, you can follow this &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/efaq/Installing-Emacs.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Now you can install the Spacemacs distribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/syl20bnr/spacemacs ~/.emacs.d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open Emacs..&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvjw5y2i6yevl142y53h8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvjw5y2i6yevl142y53h8.png" width="800" height="699"&gt;&lt;/a&gt;Spacemacs Startup Screen&lt;/p&gt;

&lt;h3&gt;
  
  
  The Basics
&lt;/h3&gt;

&lt;p&gt;Spacemacs has the concept oflayers . The idea is to have a collection of packages with some configurations. The documentation says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Layer: A collected unit of configuration that can be enabled (or disabled) in Spacemacs. A layer typically brings together one or more packages, as well as the glue configuration code required to make them play well with each other and Spacemacs in general.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you open Emacs, enter SPC f e d, this will take you to Spacemacs configuration file (~/.spacemacs). Here you can add layers inside dotspacemacs-configuration-layers. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotspacemacs-configuration-layers
 '(
 elixir
 (ruby :variables ruby-version-manager 'rvm)
 ruby-on-rails
 )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds layers for working with Elixir, Ruby and Ruby on Rails&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigating through a file
&lt;/h3&gt;

&lt;p&gt;To navigate on the file, I use the Vim commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gg =\&amp;gt; goes to the top of the file
G =\&amp;gt; goes to the bottom
h =\&amp;gt; left
j =\&amp;gt; down
k =\&amp;gt; up
l =\&amp;gt; right
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(to learn more about navigation, &lt;a href="http://vim.wikia.com/wiki/Moving_around" rel="noopener noreferrer"&gt;read here&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading your project
&lt;/h3&gt;

&lt;p&gt;After loaded, you can use SPC a d (or File -&amp;gt; Open Directory) to open your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz04vo5l45jnsg5yva4fn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz04vo5l45jnsg5yva4fn.png" width="352" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will load this buffer and you can find your project file. Hit Enter to load it&lt;/p&gt;

&lt;p&gt;Spacemacs includes &lt;a href="https://github.com/bbatsov/projectile" rel="noopener noreferrer"&gt;Projectile&lt;/a&gt;, an Emacs library for handling projects. So, after you added your project the first time with SPC a d , you can use SPC p p to Switch between projects&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furbang0fbf5d2vtpfkug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furbang0fbf5d2vtpfkug.png" width="350" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminology
&lt;/h3&gt;

&lt;p&gt;I like to use this image to understand Emacs terminology&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvz0opss79hzxyojuwdiu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvz0opss79hzxyojuwdiu.jpg" width="800" height="401"&gt;&lt;/a&gt;&lt;a href="https://emacs.stackexchange.com/questions/13583/whats-the-difference-between-a-buffer-a-file-a-window-and-a-frame" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://emacs.stackexchange.com/questions/13583/whats-the-difference-between-a-buffer-a-file-a-window-and-a-frame" rel="noopener noreferrer"&gt;https://emacs.stackexchange.com/questions/13583/whats-the-difference-between-a-buffer-a-file-a-window-and-a-frame&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Usually in most applications we call the running instance as window, but on Emacs we call it the Frame . A frame can contain multiple Windows and inside each window we have a Buffer loaded (on the image above, the buffer loaded is indicated by the yellow circle). A buffer can be the content of a loaded file or the output of some command execution. Every time you open a file, it changes the active buffer to this file, but the buffer with the past file opened is still alive. You can use SPC b b to see all buffers and switch them&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2307tw7jtd2zvqqk0hgo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2307tw7jtd2zvqqk0hgo.gif" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can easily switch between the last used buffer with SPC TAB&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f69wyzt908xxfzfurh1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f69wyzt908xxfzfurh1.gif" width="1436" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Searching for a file
&lt;/h3&gt;

&lt;p&gt;The first thing that I learn in a new editor is: How can I search for a file?&lt;/p&gt;

&lt;p&gt;In Spacemacs we can do a fuzzy file search pressing SPC p f&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl6jt65zegeruzyenloh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl6jt65zegeruzyenloh.gif" width="1916" height="1034"&gt;&lt;/a&gt;Fuzzy file search&lt;/p&gt;

&lt;p&gt;Another way is to search for Classes and methods using Tags. SPC p G will generate a TAGS file for your project. After generated, you can search through tags with SPC p g&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4xkkr4liytsc9nypp8w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4xkkr4liytsc9nypp8w.gif" width="1914" height="1033"&gt;&lt;/a&gt;Searching by Class/Method Name using Tags&lt;/p&gt;

&lt;p&gt;You can also open a tree explorer using SPC p t&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zo3ibrnv9irypjv3khp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zo3ibrnv9irypjv3khp.gif" width="800" height="475"&gt;&lt;/a&gt;SPC p t will open the tree explorer&lt;/p&gt;

&lt;h3&gt;
  
  
  Splitting window
&lt;/h3&gt;

&lt;p&gt;Another feature that I use a lot is splitting the window. You can use the Emacs keybinding to do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPC w - =\&amp;gt; Split window into two windows, one above the other.
SPC w / =\&amp;gt; Split window into two windows, side by side.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or using the vim commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:sp =\&amp;gt; Split window into two windows, one above the other.
:vsp =\&amp;gt; Split window into two windows, side by side.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzn57gfqk4kvrne0htip.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzn57gfqk4kvrne0htip.gif" width="1431" height="851"&gt;&lt;/a&gt;Splitting the window&lt;/p&gt;

&lt;p&gt;After splitting, you will notice that a number is defined in the window&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49b8u5togasvsbrj9urg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49b8u5togasvsbrj9urg.png" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To jump into a window, you can press SPC NUMBER to go to that window (for example, SPC 3 take me to the right window on the image)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkh9fof4xpt3qkbww05o5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkh9fof4xpt3qkbww05o5.gif" width="800" height="474"&gt;&lt;/a&gt;Switching between windows&lt;/p&gt;

&lt;p&gt;You can kill a window with SPC w d or :q .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ufn0xgjc1iti1wuqnpq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ufn0xgjc1iti1wuqnpq.gif" width="1427" height="851"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes when working on more than one window in the same frame (for example, having a class and a test file opened together), I like to maximize one of them. You can use SPC w m to maximize and use it again to return it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps2ldsowbs98zye1rqvb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps2ldsowbs98zye1rqvb.gif" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  My Workflow working with it
&lt;/h3&gt;

&lt;p&gt;Usually, my workflow with Vim involves Tmux too. I typically have 3 Tmux panes open. One for the editor (Vim), another for the terminal (to run tests, ssh, etc), and one for the server (and the logs).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4puy0zzhh2mqm0594i2d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4puy0zzhh2mqm0594i2d.gif" width="1434" height="849"&gt;&lt;/a&gt;My workflow with Vim and tmux&lt;/p&gt;

&lt;p&gt;I also like to use &lt;a href="https://github.com/janko-m/vim-test" rel="noopener noreferrer"&gt;vim-test&lt;/a&gt; to run my tests inside Vim&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F419bk4tsb04bpn5sbxat.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F419bk4tsb04bpn5sbxat.gif" width="1434" height="849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Emacs for another side, have the idea that you can do everything on the Emacs itself. You can start your server on it, open a terminal, etc, without having the needing to have multiple terminals for each thing (although I still have they open when working on Emacs 😄)&lt;/p&gt;

&lt;p&gt;My next examples use the rails layer on Spacemacs.&lt;/p&gt;

&lt;p&gt;I can run bundle install (to install my ruby dependencies) inside it with SPC m b i&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx97vegyyoym7mrxkjhew.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx97vegyyoym7mrxkjhew.gif" width="1436" height="851"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can run the rails server inside it and check the logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qbvm8y6iiqm5axs05td.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qbvm8y6iiqm5axs05td.gif" width="760" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and also run the tests directly from Emacs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb86832x28f8n7tpul1g6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb86832x28f8n7tpul1g6.gif" width="1433" height="852"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow a simple mindmap that I created for these basic commands:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2um72onc65segx4vgkq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2um72onc65segx4vgkq0.png" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is all the basics that I need to use it and that I want to share.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0evztrkom7nlowjwcvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0evztrkom7nlowjwcvz.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cheers 🍻&lt;/p&gt;




</description>
      <category>emacs</category>
      <category>spacemacs</category>
      <category>vim</category>
    </item>
    <item>
      <title>Easy deploy your Docker applications to AWS using ECS and Fargate</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Wed, 31 Jan 2018 17:03:58 +0000</pubDate>
      <link>https://dev.to/caduribeiro/easy-deploy-your-docker-applications-to-aws-using-ecs-and-fargate-2ain</link>
      <guid>https://dev.to/caduribeiro/easy-deploy-your-docker-applications-to-aws-using-ecs-and-fargate-2ain</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AL_1-B_5m036MTLsyQ135cw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AL_1-B_5m036MTLsyQ135cw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, I will try to demonstrate how you can deploy your Docker application into AWS using ECS and Fargate.&lt;/p&gt;

&lt;p&gt;As an example, I will deploy &lt;a href="http://openjobs.me/" rel="noopener noreferrer"&gt;this app&lt;/a&gt; to ECS. The source can be found &lt;a href="https://github.com/opensanca/opensanca_jobs/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will use &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; to spin the infrastructure so I can easily track everything that I create as a code. If you want to learn the basics of Terraform, please read my &lt;a href="https://dev.to/duduribeiro/creating-your-cloud-servers-with-terraform-2lpd"&gt;post about it&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ECS
&lt;/h3&gt;

&lt;p&gt;What is ECS?&lt;/p&gt;

&lt;p&gt;The Elastic Container Service (ECS) is an AWS Service that handles the Docker containers orchestration in your EC2 cluster. It is an alternative for Kubernetes, Docker Swarm, and others.&lt;/p&gt;

&lt;h4&gt;
  
  
  ECS Terminology
&lt;/h4&gt;

&lt;p&gt;To start understanding what ECS is, we need to understand its terms and definitions that differs from the Docker world.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cluster:&lt;/strong&gt; It is a group of EC2 instances hosting containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task definition:&lt;/strong&gt; It is the specification of how ECS should run your app. Here you define which image to use, port mapping, memory, environments variables, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service:&lt;/strong&gt; Services launches and maintains tasks running inside the cluster. A Service will auto-recover any stopped tasks keeping the number of tasks running as you specified.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fargate
&lt;/h3&gt;

&lt;p&gt;Fargate is a technology that allows running containers in ECS without needing to manage the EC2 servers for cluster. You only deploy your Docker applications and set the scaling rules for it. Fargate is an execution method from ECS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Show me the code&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full example is on &lt;a href="https://github.com/duduribeiro/terraform_ecs_fargate_example" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The project structure
&lt;/h3&gt;

&lt;p&gt;Our Terraform project is composed of the following structure:&lt;/p&gt;

&lt;p&gt;├── modules&lt;br&gt;&lt;br&gt;
│ └── code_pipeline&lt;br&gt;&lt;br&gt;
│ └── ecs&lt;br&gt;&lt;br&gt;
│ └── networking&lt;br&gt;&lt;br&gt;
│ └── rds&lt;br&gt;&lt;br&gt;
├── pipeline.tf&lt;br&gt;&lt;br&gt;
├── production.tf&lt;br&gt;&lt;br&gt;
├── production_key.pub&lt;br&gt;&lt;br&gt;
├── terraform.tfvars&lt;br&gt;&lt;br&gt;
└── variables.tf&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modules&lt;/strong&gt; are where we will store the code that handles the creation of a group of resources. It can be reused by all environments (Production, Staging, QA, etc.) without needing to duplicate a lot of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;production.tf&lt;/strong&gt; is the file that defines the environment itself. It calls the modules passing variables to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pipeline.tf&lt;/strong&gt; Since the pipeline can be a global resource without needing to isolate per environment. This file will handle the creation of this pipeline using the &lt;code&gt;code_pipeline&lt;/code&gt; module.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  First part, the networking
&lt;/h3&gt;

&lt;p&gt;The branch with this part can be found &lt;a href="https://github.com/duduribeiro/terraform_ecs_fargate_example/tree/01_networking" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first thing that we need to create is the VPC with 2 subnets (1 public and 1 private) in each Availability Zone. Each Availability Zone is a geographically isolated region. Keeping our resources in more than one zone is the first thing to achieve high availability. If one physical zone fails for some reason, your application can answer from the others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ACvu1YNJdfezuVfU8kAPgNA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ACvu1YNJdfezuVfU8kAPgNA.png"&gt;&lt;/a&gt;Our networking&lt;/p&gt;

&lt;p&gt;Keeping the cluster on the private subnet protects your infrastructure from external access. The private subnet is allowed only to be accessed from resources inside the public network (In our case, will be the Load Balancer only).&lt;/p&gt;

&lt;p&gt;This is the code to create this structure (it is practically the same from my introduction post of Terraform):&lt;/p&gt;

&lt;a href="https://medium.com/media/3000fa3a6cfa0ccdb9678d3e4660424d/href" rel="noopener noreferrer"&gt;https://medium.com/media/3000fa3a6cfa0ccdb9678d3e4660424d/href&lt;/a&gt;

&lt;p&gt;The above code creates the VPC, 4 subnets (2 public and 2 private) in each Availability zone. It also creates a NAT to allow the private network access the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The branch with this part can be found &lt;a href="https://github.com/duduribeiro/terraform_ecs_fargate_example/tree/02_database" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We will create a RDS database. It will be located on the private subnet. Allowing only the public subnet to access it.&lt;/p&gt;

&lt;a href="https://medium.com/media/59d7deafef35b800131d6d8061ad1e82/href" rel="noopener noreferrer"&gt;https://medium.com/media/59d7deafef35b800131d6d8061ad1e82/href&lt;/a&gt;

&lt;p&gt;With this code, we create the RDS resource with values received from the variables. We also create the security group that should be used by resources that want to connect to the database (in our case, the ECS cluster).&lt;/p&gt;

&lt;p&gt;Ok. Now we have the database. Let’s finally create our ECS to deploy our app \o/.&lt;/p&gt;
&lt;h3&gt;
  
  
  Take Three: The ECS
&lt;/h3&gt;

&lt;p&gt;The branch with this part can be found &lt;a href="https://github.com/duduribeiro/terraform_ecs_fargate_example/tree/03_ecs" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We are approaching the final steps. Now, it is the part that we define the ECS resources needed for our app.&lt;/p&gt;
&lt;h4&gt;
  
  
  The ECR repository
&lt;/h4&gt;

&lt;p&gt;The first thing is to create the repository to store our built images.&lt;/p&gt;

&lt;a href="https://medium.com/media/356acc348a4487a6434c6e60919f7c62/href" rel="noopener noreferrer"&gt;https://medium.com/media/356acc348a4487a6434c6e60919f7c62/href&lt;/a&gt;
&lt;h4&gt;
  
  
  The ECS cluster
&lt;/h4&gt;

&lt;p&gt;Next, we need our ECS cluster. Even using Fargate (that doesn’t need any EC2), we need to define a cluster for the application.&lt;/p&gt;

&lt;a href="https://medium.com/media/007965a6291dc6a49086c14471cdd92f/href" rel="noopener noreferrer"&gt;https://medium.com/media/007965a6291dc6a49086c14471cdd92f/href&lt;/a&gt;
&lt;h4&gt;
  
  
  The tasks definitions
&lt;/h4&gt;

&lt;p&gt;Now, we will define 2 task definitions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web: Contains the definition of the web app itself.&lt;/li&gt;
&lt;li&gt;Db Migrate: This task will only run the command to migrate our database and will die. Since it is a single run task, we don’t need a service for it.
&lt;a href="https://medium.com/media/cbba359b380baf0de6398ebbf29d66c0/href" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/cbba359b380baf0de6398ebbf29d66c0/href" rel="noopener noreferrer"&gt;https://medium.com/media/cbba359b380baf0de6398ebbf29d66c0/href&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tasks definitions are configured in a JSON file and rendered as a template in Terraform.&lt;br&gt;&lt;br&gt;
This is the task definition of the web app:&lt;/p&gt;

&lt;a href="https://medium.com/media/cd914e0e8af12d8d338da2acd81e85d2/href" rel="noopener noreferrer"&gt;https://medium.com/media/cd914e0e8af12d8d338da2acd81e85d2/href&lt;/a&gt;

&lt;p&gt;In the file above, we are defining the task to ECS. We pass the created ECR image repository as variable to it. We also configure other variables so ECS can start our Rails app.&lt;br&gt;&lt;br&gt;
The definition of the DB migration task is almost the same. We only change the command that will be executed.&lt;/p&gt;
&lt;h4&gt;
  
  
  The load balancers
&lt;/h4&gt;

&lt;p&gt;Before creating the Services, we need to create the load balancers. They will be on the public subnet and will forward the requests to the ECS service.&lt;/p&gt;

&lt;a href="https://medium.com/media/3033baa23cf2903207443201d7762619/href" rel="noopener noreferrer"&gt;https://medium.com/media/3033baa23cf2903207443201d7762619/href&lt;/a&gt;

&lt;p&gt;In the file above we define that our target group will use HTTP on port 80. We also create a security group to allow access into the port 80 from the internet. After, we create the Application Load Balancer and the listener. To use Fargate, you should use an Application Load Balancer instead an Elastic Load Balancer.&lt;/p&gt;
&lt;h4&gt;
  
  
  Finally, the ECS service
&lt;/h4&gt;

&lt;p&gt;Now we will create the service. To use Fargate, we need to specify the lauch_type as Fargate.&lt;/p&gt;

&lt;a href="https://medium.com/media/9a7598bcb9d94c58ca13ca1be88a7d69/href" rel="noopener noreferrer"&gt;https://medium.com/media/9a7598bcb9d94c58ca13ca1be88a7d69/href&lt;/a&gt;
&lt;h4&gt;
  
  
  Auto-scaling
&lt;/h4&gt;

&lt;p&gt;Fargate allows us to auto-scale our app easily. We only need to create the metrics in CloudWatch and trigger to scale it up or down.&lt;/p&gt;

&lt;a href="https://medium.com/media/0163c1beacb8ade34e9f09209dd80916/href" rel="noopener noreferrer"&gt;https://medium.com/media/0163c1beacb8ade34e9f09209dd80916/href&lt;/a&gt;

&lt;p&gt;We create 2 auto scaling policies. One to scale up and other to scale down the desired count of running tasks from our ECS service.&lt;/p&gt;

&lt;p&gt;After, we create a CloudWatch metric based on the CPU. If the CPU usage is greater than 85% from 2 periods, we trigger the alarm_action that calls the scale-up policy. If it returns to the Ok state, it will trigger the scale-down policy.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Pipeline to deploy our app
&lt;/h3&gt;

&lt;p&gt;Our infrastructure to run our Docker app is ready. But it is still boring to deploy it to ECS. We need to manually push our image to the repository and update the task definition with the new image and update the new task definition. We can run it through Terraform, but it could be better if we have a way to push our code to Github in the master branch and it deploys automatically for us.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Entering,&lt;/em&gt; &lt;a href="https://aws.amazon.com/codepipeline" rel="noopener noreferrer"&gt;&lt;em&gt;CodePipeline&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; &lt;a href="https://aws.amazon.com/codebuild/" rel="noopener noreferrer"&gt;&lt;em&gt;CodeBuild&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;CodePipeline is a Continuous Integration and Continuous Delivery service hosted by AWS.&lt;/p&gt;

&lt;p&gt;CodeBuild is a managed build service that can execute tests and generate packages for us (in our case, a Docker image).&lt;/p&gt;

&lt;p&gt;With it, we can create pipelines to delivery our code to ECS. The flow will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You push the code to master’s branch&lt;/li&gt;
&lt;li&gt;CodePipeline gets the code in the Source stage and calls the Build stage (CodeBuild).&lt;/li&gt;
&lt;li&gt;Build stage process our Dockerfile building and pushing the Image to ECR and triggers the Deploy stage&lt;/li&gt;
&lt;li&gt;Deploy stage updates our ECS with the new image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s define our Pipeline with Terraform:&lt;/p&gt;

&lt;a href="https://medium.com/media/68275343cb24ec50baf32ce6cc1002e3/href" rel="noopener noreferrer"&gt;https://medium.com/media/68275343cb24ec50baf32ce6cc1002e3/href&lt;/a&gt;

&lt;p&gt;In the above code, we create a CodeBuild project, using the following buildspec (build specifications file):&lt;/p&gt;

&lt;a href="https://medium.com/media/40583c74d4308338360de35bb99a1921/href" rel="noopener noreferrer"&gt;https://medium.com/media/40583c74d4308338360de35bb99a1921/href&lt;/a&gt;

&lt;p&gt;We defined some phases in the above file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pre_build: Upgrade aws-cli, set some environment variables: REPOSITORY_URL with the ECR repository and IMAGE_TAG with the CodeBuild source version. The ECR repository is passed as a variable by Terraform.&lt;/li&gt;
&lt;li&gt;build: Build the Dockerfile from the repository tagging it as LATEST in the repository URL.&lt;/li&gt;
&lt;li&gt;post_build: Push the image to the repository. Creates a file named imagedefinitions.json with the following content:
‘[{“name”:”web”,”imageUri”:REPOSITORY_URL”}]’
This file is used by CodePipeline to upgrade your ECS cluster in the Deployment stage.&lt;/li&gt;
&lt;li&gt;artifacts: Get the file created in the last phase and uses as the artifact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After, we create a CodePipeline resource with 3 stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source: Gets the repository from Github (change it by your repository information) and pass it to the next stage.&lt;/li&gt;
&lt;li&gt;Build: Calls the CodeBuild project that we created in the step before.&lt;/li&gt;
&lt;li&gt;Production: Gets the artifact from Build stage (imagedefinitions.json) and deploy to ECS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see they working together?&lt;/p&gt;
&lt;h3&gt;
  
  
  Running all together
&lt;/h3&gt;

&lt;p&gt;The code with the full example is &lt;a href="https://github.com/duduribeiro/terraform_ecs_fargate_example" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Clone it. Also, since we use Github as the CodePipeline source provider, you need to generate a token to access the repositories. &lt;a href="[https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)"&gt;Read here&lt;/a&gt; to generate yours.&lt;/p&gt;

&lt;p&gt;After generating your token, export it as an environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ export GITHUB\_TOKEN=YOUR\_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we need to import the modules and the provider library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A6HQQkkurojqHSIw3ywQTSw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A6HQQkkurojqHSIw3ywQTSw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let the magic begin!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it will display that Terraform will create some resources, and if you want to continue&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F812%2F1%2AN_Ce_3nRV-hk4QFgd-J3sw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F812%2F1%2AN_Ce_3nRV-hk4QFgd-J3sw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type yes.&lt;/p&gt;

&lt;p&gt;Terraform will start create our infraestructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F260%2F1%2AlZ7NXzq0NMQmObgMoyoJIg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F260%2F1%2AlZ7NXzq0NMQmObgMoyoJIg.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seriously, get a coffee until it finishes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2Ahl4G1GBfzMSBuBJ2axn0LQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2Ahl4G1GBfzMSBuBJ2axn0LQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A-w-oLVLxuyebsaUStamQHg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A-w-oLVLxuyebsaUStamQHg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWESOME!. Our infrastructure is ready!!. If you enter in your CodePipeline at AWS Dashboard, you can see that it also triggered the first build:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AXfmkQU8ae8v9Pbp6iaJQIA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AXfmkQU8ae8v9Pbp6iaJQIA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait until all the Stages are green.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F456%2F1%2AZdNh3pr5qIdU-vMwlQfi4Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F456%2F1%2AZdNh3pr5qIdU-vMwlQfi4Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get your Load Balancer DNS and check the deployed application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ terraform output alb\_dns\_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AWW07XGYH37It1xq5tUnGrQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AWW07XGYH37It1xq5tUnGrQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AwJmKvO2Pd36jtJMZePd-3Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AwJmKvO2Pd36jtJMZePd-3Q.png"&gt;&lt;/a&gt;It is working \o/&lt;/p&gt;

&lt;p&gt;Finally, the app is running. Almost magic!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F275%2F1%2AmPUc2fU1VPbW6gjbw1DjeQ.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F275%2F1%2AmPUc2fU1VPbW6gjbw1DjeQ.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have any questions about it, contact me. This was just an introduction post about ECS with Fargate using Terraform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AdsHpznpcd482MHT1fvyc3Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AdsHpznpcd482MHT1fvyc3Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cheers 🍻&lt;/p&gt;




</description>
      <category>docker</category>
      <category>terraform</category>
      <category>awsecs</category>
      <category>aws</category>
    </item>
    <item>
      <title>Evaluate your ruby code directly from VIM</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Mon, 07 Aug 2017 21:41:45 +0000</pubDate>
      <link>https://dev.to/caduribeiro/evaluate-your-ruby-code-directly-from-vim-1oag</link>
      <guid>https://dev.to/caduribeiro/evaluate-your-ruby-code-directly-from-vim-1oag</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F476%2F1%2A-t37W7RSogM604xODOKokw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F476%2F1%2A-t37W7RSogM604xODOKokw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I am writing code, usually I want to evaluate some piece of code. I used to do the following actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy and paste my code to IRB (or run my ruby script file directly from the terminal).&lt;/li&gt;
&lt;li&gt;When using tmux, send my code directly from vim to tmux with the &lt;a href="https://github.com/christoomey/vim-tmux-runner" rel="noopener noreferrer"&gt;vim-tmux-runner&lt;/a&gt; plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first option needs an extra work of copying and pasting content. I prefer the second option, but sometimes I forgot to attach the VTR pane and get errors.&lt;/p&gt;

&lt;p&gt;Now I’m using &lt;a href="https://github.com/JoshCheek/seeing_is_believing" rel="noopener noreferrer"&gt;https://github.com/JoshCheek/seeing_is_believing&lt;/a&gt; along with &lt;a href="https://github.com/t9md/vim-ruby-xmpfilter" rel="noopener noreferrer"&gt;https://github.com/t9md/vim-ruby-xmpfilter&lt;/a&gt; plugin&lt;/p&gt;

&lt;p&gt;I set my .vimrc with the following content (I use Plug to manage my dependencies):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plug 't9md/vim-ruby-xmpfilter'

" Enable seeing-is-believing mappings only for Ruby
let g:xmpfilter\_cmd = "seeing\_is\_believing"

autocmd FileType ruby nmap \&amp;lt;buffer\&amp;gt; \&amp;lt;F4\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-mark)
autocmd FileType ruby xmap \&amp;lt;buffer\&amp;gt; \&amp;lt;F4\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-mark)
autocmd FileType ruby imap \&amp;lt;buffer\&amp;gt; \&amp;lt;F4\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-mark)

autocmd FileType ruby nmap \&amp;lt;buffer\&amp;gt; \&amp;lt;F6\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-clean)
autocmd FileType ruby xmap \&amp;lt;buffer\&amp;gt; \&amp;lt;F6\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-clean)
autocmd FileType ruby imap \&amp;lt;buffer\&amp;gt; \&amp;lt;F6\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-clean)

autocmd FileType ruby nmap \&amp;lt;buffer\&amp;gt; \&amp;lt;F5\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-run)
autocmd FileType ruby xmap \&amp;lt;buffer\&amp;gt; \&amp;lt;F5\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-run)
autocmd FileType ruby imap \&amp;lt;buffer\&amp;gt; \&amp;lt;F5\&amp;gt; \&amp;lt;Plug\&amp;gt;(seeing\_is\_believing-run)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I can visual select my code, use F4 to mark and that line will be evaluated, press F5 and get the result of that code. After, I can clean all marks with F6 .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F922%2F1%2A7gjSHyVfzMhsoa038YicQg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F922%2F1%2A7gjSHyVfzMhsoa038YicQg.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;&lt;br&gt;
🍻&lt;/p&gt;




</description>
      <category>ruby</category>
      <category>vim</category>
    </item>
    <item>
      <title>Creating review apps per pull requests</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Tue, 04 Jul 2017 11:44:00 +0000</pubDate>
      <link>https://dev.to/caduribeiro/creating-review-apps-per-pull-requests-29li</link>
      <guid>https://dev.to/caduribeiro/creating-review-apps-per-pull-requests-29li</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F300%2F1%2AJKrc7lJZGa3_R1jA8yXxuQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F300%2F1%2AJKrc7lJZGa3_R1jA8yXxuQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post I will show a simple example about how to create apps for each pull request (or, creating your own Gitlab/Heroku Review App).&lt;/p&gt;

&lt;p&gt;Let’s imagine the following scenario of a development team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developer creates a new feature branch.&lt;/li&gt;
&lt;li&gt;Developer pushes the branch&lt;/li&gt;
&lt;li&gt;Developer opens a pull request so other developers can check his code and test the feature&lt;/li&gt;
&lt;li&gt;CI runs the tests and makes the branch green if it passes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the developer should send the feature to someone else to test it. The feature should not be merged into master so it can be tested. They should be tested isolated because one feature can interfere with another and master should only have deployable code. With this in mind, we will create an environment for each pull request using Docker.&lt;/p&gt;

&lt;p&gt;I will use Jenkins as CI because I want to use an open source tool to demonstrate this, but you can use any CI tool that you prefer.&lt;/p&gt;

&lt;p&gt;This post will assume that you already have Jenkins installed. I will use &lt;a href="https://github.com/opensanca/opensanca_jobs" rel="noopener noreferrer"&gt;this&lt;/a&gt; opensource Rails application as an example. Fork this repo into your account and clone it in your computer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone [https://github.com/yourgithubusername/opensanca\_jobs.git](https://github.com/duduribeiro/openjobs_experiment.git)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can get the full code repo that I used on &lt;a href="https://github.com/duduribeiro/openjobs_jenkins_test" rel="noopener noreferrer"&gt;https://github.com/duduribeiro/openjobs_jenkins_test&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Configuring Docker
&lt;/h3&gt;

&lt;p&gt;The first step that we need is to configuring our app with docker.&lt;/p&gt;

&lt;p&gt;A Dockerfile is a file that has instructions to build an image that contains our application. You can learn more about Docker in their &lt;a href="https://docs.docker.com" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a file named Dockerfile in the app folder:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This is our instructions to build an image. The first command FROM specifies to docker that we will use the image &lt;a href="https://hub.docker.com/r/_/ruby/" rel="noopener noreferrer"&gt;ruby:2.4.1&lt;/a&gt; as our base image.&lt;/p&gt;

&lt;p&gt;After, the first RUN command installs all dependencies the app needs: yarn, imagemagick and node (to precompile the assets. A fancy solution is to use a different container only with node and sprockets to precompile the assets). The second RUN command, creates a folder /var/app that will be responsible to store the application. The COPY command moves the current folder to the container in the /var/app folder. The next RUN command installs all dependencies from Rails and yarn. The CMD specify the command that the container should execute when it runs the image. You can learn more about Dockerfile &lt;a href="https://docs.docker.com/engine/reference/builder/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this file we can build our image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t myimage .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this command, we are building our Dockerfile and generating an image with the tag myimage .&lt;/p&gt;

&lt;p&gt;Running docker image ls we can check our image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F520%2F1%2Aq2LJXXVz4fAXwOD2wp2jjA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F520%2F1%2Aq2LJXXVz4fAXwOD2wp2jjA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can start our application:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d -p 3000:3000 myimage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The -d option tells to docker that this container will run in background and -p 3000:3000 will connect the local port 3000 with the exposed 3000 port from the container.&lt;/p&gt;

&lt;p&gt;We can navigate now to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AfT58lZr0Pz8zUfEnn43y1Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AfT58lZr0Pz8zUfEnn43y1Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we receive this error. This is because our app requires a database connection. We need to run a new container with the database and link both containers so they can communicate with each other. Each container should have only one purpose, so, you should not run more than 1 service in a single container (ie: a container with the application and the database).&lt;/p&gt;

&lt;p&gt;Instead of manually run each container, we will use &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;docker compose&lt;/a&gt; , a tool to help us to run multi containers applications.&lt;/p&gt;

&lt;p&gt;Use docker ps and docker kill to destroy your application container/&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F414%2F1%2AM2HI_2fGRmo8knwM9VGQgQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F414%2F1%2AM2HI_2fGRmo8knwM9VGQgQ.png"&gt;&lt;/a&gt;Get the id of the container&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F374%2F1%2A3GpKXZWWfJIR5Qa603m_0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F374%2F1%2A3GpKXZWWfJIR5Qa603m_0w.png"&gt;&lt;/a&gt;Kill the container&lt;/p&gt;

&lt;p&gt;Create a docker-compose.yml file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;In this file, we are creating 2 services. One for the database using the postgres image, and another one with our web application using our Dockerfile image.&lt;/p&gt;

&lt;p&gt;In the environment item on web service, I’m setting the DATABASE_URL environment variable. If this environment variable is set, rails will use it replacing the loaded configurations from config/database.yml. Read more about this &lt;a href="http://edgeguides.rubyonrails.org/configuring.html#configuring-a-database" rel="noopener noreferrer"&gt;here&lt;/a&gt;. DATABASE_URL follows this pattern for postgres:&lt;br&gt;&lt;br&gt;
 postgres://user:password@host/database_name.Since our database user does not have password, we leave empty after the : . We don’t fill the database_name because we want the configured one in config/database.yml (one database name per environment).&lt;/p&gt;

&lt;p&gt;Create the databases and run the migrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose run --rm web rake db:create db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this command, we will run a container with the web service and execute the command rake db:create db:migrate . The --rm option is to remove the container after the execution.&lt;/p&gt;

&lt;p&gt;Run the tests:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose run --rm -e RAILS\_ENV=test web rake db:drop db:create db:migrate

docker-compose run --rm web rspec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AqzMCx1X0ZNlKkKNYSsN3MA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AqzMCx1X0ZNlKkKNYSsN3MA.png"&gt;&lt;/a&gt;Success&lt;/p&gt;

&lt;p&gt;Now we can start the application:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Access &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; again and now our app is working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ARlLvjbA3ZZL6CR5U2hBKeQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ARlLvjbA3ZZL6CR5U2hBKeQ.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Configuring our pipeline
&lt;/h3&gt;

&lt;p&gt;We will use Jenkins pipeline as code to configure our pipeline. Read more about it &lt;a href="https://jenkins.io/solutions/pipeline/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. We need to have &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;docker&lt;/a&gt;, &lt;a href="https://stedolan.github.io/jq/" rel="noopener noreferrer"&gt;jq&lt;/a&gt; and Jenkins installed on CI server.&lt;/p&gt;

&lt;p&gt;We will use the following Jenkins plugins: &lt;a href="https://wiki.jenkins-ci.org/display/JENKINS/Blue+Ocean+Plugin" rel="noopener noreferrer"&gt;Blue Ocean&lt;/a&gt;, &lt;a href="https://wiki.jenkins-ci.org/display/JENKINS/BlueOcean+Pipeline+Editor+Plugin" rel="noopener noreferrer"&gt;Blue Ocean Pipeline Editor&lt;/a&gt;, &lt;a href="https://wiki.jenkins-ci.org/display/JENKINS/Blue+Ocean+Plugin" rel="noopener noreferrer"&gt;GitHub Pipeline for Blue Ocean&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a file named Jenkinsfile in the project root with the following content:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;We have 4 stages on this pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt; : It will build our Dockerfile and generate an image from this build tagged with openjobs with version named latest . It will use docker-compose to build, install dependencies, create and migrate the database created with compose .&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt; : It will run 2 parallel steps, one to run unit tests, and another to run feature tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to staging&lt;/strong&gt; : If it is the master branch, it will deploy the app to staging.domain . We will cover this in the next steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create feature environment&lt;/strong&gt; : If it isn’t the master branch, it will deploy the app to branchname.domain . We will cover this in the next steps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s create our pipeline on Jenkins. Push your code, go to your Jenkins, and access the blue ocean interface and click in the New Pipeline button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AeXA3HCPFSBQ-mvUNWHV8gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AeXA3HCPFSBQ-mvUNWHV8gw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next screen, select Github, choose you account and find the repository. Click in Create Pipeline&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A_2gQbUaarl7LFVDOKNcqxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A_2gQbUaarl7LFVDOKNcqxw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And our build is passing 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A--GrJVBvaS8b7WMavEazew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A--GrJVBvaS8b7WMavEazew.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The red icon on the build is because this step will not run, since the branch built was master .&lt;/p&gt;

&lt;p&gt;Now we need to create our environments after the build and dynamic route a domain to a specific container.&lt;/p&gt;

&lt;p&gt;Entering, &lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;Traefik&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F340%2F1%2A0A8y77LMVEQtxLSQ_J0zJA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F340%2F1%2A0A8y77LMVEQtxLSQ_J0zJA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Traefik is a tool that will help us make the dynamic routing and act as a load balancer. Example: If I access &lt;a href="http://mybranch.mydomain.com" rel="noopener noreferrer"&gt;http://mybranch.mydomain.com&lt;/a&gt; , I want to access the container containing the app with mybranch that should be started by Jenkins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating dynamic environments
&lt;/h3&gt;

&lt;p&gt;The following steps should be executed on CI server.&lt;/p&gt;

&lt;p&gt;We will use &lt;a href="https://docs.docker.com/engine/swarm/" rel="noopener noreferrer"&gt;Docker Swarm&lt;/a&gt;. It is very helpful to create a docker cluster. I will use only one server to demonstrate, but you are able also to create a cluster.&lt;/p&gt;

&lt;p&gt;Initialize swarm cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker swarm init --advertise-addr=10.10.0.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will initialize our server as the swarm master. It will generate a command that you can use to join the cluster in other servers.&lt;/p&gt;

&lt;p&gt;Create a network that we can use with traefik and our containers:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network create --driver=overlay --attachable traefik-net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Initialize traefik:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker service create \
--name traefik \
--constraint 'node.role==manager' \
--publish 80:80 \
--publish 8081:8080 \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--network traefik-net \
traefik \
--docker \
--docker.swarmmode \
--docker.domain=apps.carlosribeiro.me \
--docker.watch \
--logLevel=DEBUG \
--web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will initialize traefik. You should treat this initialization on some startup script on your server.&lt;/p&gt;

&lt;p&gt;If we access our server in 8081 port, we can access traefik dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ALet1rhq73CWwjCyWSZgKLw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ALet1rhq73CWwjCyWSZgKLw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The docker.domain specify that we will access the apps through appname.apps.carlosribeiro.me . To allow this, I created two alias on my DNS server pointing to the CI server’s IP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apps.carlosribeiro.me&lt;/li&gt;
&lt;li&gt;*.apps.carlosribeiro.me&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Editing Jenkinsfile to create the environments
&lt;/h3&gt;

&lt;p&gt;Lets add a method on Jenkinsfile that will create a environment when the build is triggered.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This method will accept an argument to inform the name of the environment. This name will be used to prefix the services name. It will stop all docker-compose containers that are still running. After, it will remove if exist, services with the same name (This is to recreate the environment when we push again to the branch). After, we create 3 services. One for Postgres, another for Redis, and the app itself using the imaged that we built on build step. The last command, runs a temporary docker container to create and migrate the database and precompile the assets (you can fetch your previously dump from production too).&lt;/p&gt;

&lt;p&gt;Look that in the app service, we specify some environment variables.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;REDIS_URL, DATABASE_URL&lt;/strong&gt; : Endpoint to connect in both services using the previously created services hosts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAILS_ENV:&lt;/strong&gt; We set it to production . It will enforce that our app should behave like a production environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAILS_SERVE_STATIC_FILES&lt;/strong&gt; : Since we will not have a nginx in front of the app server, we need to set this to tell rails to service static files for us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;— label ‘traefik.port’ will inform traefik what is the container’s exposed port. In our case, is the 3000.&lt;/p&gt;

&lt;p&gt;— name inform the service name that traefik will identify as the prefix on domain.&lt;/p&gt;

&lt;p&gt;Now, lets call this method on our pipeline’s stages.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;If it is master branch, we will deploy into an app called staging . If not, it will call the app with the same name of branch.&lt;/p&gt;

&lt;p&gt;Look the final Jenkinsfile:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Push master and let’s wait Jenkins create our staging for us.&lt;/p&gt;

&lt;p&gt;Our build passes…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AdXnyUalMlB61BcsFeyDApA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AdXnyUalMlB61BcsFeyDApA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;… and our app is deployed on staging:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A160K0xkSl3dJ83duQ5Mq8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A160K0xkSl3dJ83duQ5Mq8g.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s open a pull request to change this button color.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout -b 01-change-button-color
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit app/assets/stylesheets/application.scss&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.btn-register-vacancy {
  background-color: #2769ff;
  width: 300px;
  height: 35px;
  border-radius: 5px;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push this branch, and open a pull request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AeLQPHGOVDArlDDrvPmnLXQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AeLQPHGOVDArlDDrvPmnLXQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github will tell us that the build is pending.&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;Job is passing&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AvZwLnuEbj8egwCvUQ8cAIg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AvZwLnuEbj8egwCvUQ8cAIg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and Github is informed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AW6y5kHRubVrYJgFeACdRIA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AW6y5kHRubVrYJgFeACdRIA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And now we have our dynamic environment up and running&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AM_n7-CK4mquX42LkpM_pmQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AM_n7-CK4mquX42LkpM_pmQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can send this URL to a QA or a PO review it before merging. If something is wrong, it can be validated before it is on master.&lt;/p&gt;

&lt;p&gt;That’s all&lt;/p&gt;

&lt;p&gt;Cheers 🍻&lt;/p&gt;




</description>
      <category>docker</category>
      <category>pullrequest</category>
      <category>ci</category>
    </item>
    <item>
      <title>Creating your cloud servers with Terraform</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Tue, 04 Apr 2017 14:56:49 +0000</pubDate>
      <link>https://dev.to/caduribeiro/creating-your-cloud-servers-with-terraform-2lpd</link>
      <guid>https://dev.to/caduribeiro/creating-your-cloud-servers-with-terraform-2lpd</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2A2QvMO07P4smlK-GE45EROA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2A2QvMO07P4smlK-GE45EROA.jpeg"&gt;&lt;/a&gt;Terraforming Mars&lt;/p&gt;

&lt;p&gt;Sometimes when you handle a lot of servers in the cloud, it is pretty easy to get lost on your infrastructure. “Where is that freaking server that I can’t find?”, or even “Why is this instance for?”.&lt;/p&gt;

&lt;p&gt;In this post, I will introduce one tool that I found myself liking a lot: To have a bootstrap of an Infrastructure as Code flow in my applications. &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;. Infrastructure as Code (IaC) allows us to have a repository with code that describes our infrastructure. This way, we can avoid reminding how rebuild the entire infrastructure for an application. It will be on the code and it can be versioned and tested. And if something goes wrong, we can revert it. It is very useful having a continuous integration of the infrastructure. The whole team knows how it was built and all the pieces. You can apply the same flow that you use in you app code to your infrastructure, i.e: Someone makes a change, open a Pull Request, someone reviews it and after it is approved, you merge and your CI tools apply the changes in your environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Terraform? What is the difference between Chef, Puppet or Ansible?
&lt;/h4&gt;

&lt;p&gt;Chef, Puppet and Ansible are IaC tools too, but they focus on &lt;strong&gt;configuring&lt;/strong&gt; operating system and applications. They are called Configuration Management Tools and they also can build infrastructure on the cloud with a help of plugins, but usually it is hard to configure and sometimes it is limited. With Terraform you can build from the services to the networking part. You can use Terraform to create the infrastructure and a configuration management tool to configure the applications. Terraform can’t replace your configuration management tool, but it’s made to work together with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F400%2F1%2AsS6MVPyxzhn3O8pA3nw0kg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F400%2F1%2AsS6MVPyxzhn3O8pA3nw0kg.jpeg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A practical example
&lt;/h3&gt;

&lt;p&gt;In order to follow this article, you’ll need an AWS account.&lt;/p&gt;

&lt;p&gt;Let’s create the servers for our web application. Take a look at this diagram of the resources that we’ll build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AnYWHvlp87BBsE6gI2TbKYA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AnYWHvlp87BBsE6gI2TbKYA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will create 2 subnets (one for public access and another private). We have Elastic Load Balancer in the public subnet to handle the traffic to our web servers. Our web servers will be on the private subnet and it will only be accessible through the Load Balancer. This mean that we won’t have direct access to make connections (for example, SSH) on the server. In order to access via SSH an instance on a private subnet, you’ll need a bastion host and connect to the web server through it. Thus, we will create the bastion host on the public subnet.&lt;/p&gt;

&lt;p&gt;Before getting your hands dirty, you need to install Terraform. Follow the instructions of &lt;a href="https://www.terraform.io/intro/getting-started/install.html" rel="noopener noreferrer"&gt;this link&lt;/a&gt; to install it in your machine.&lt;/p&gt;

&lt;h4&gt;
  
  
  Directory structure
&lt;/h4&gt;

&lt;p&gt;Let’s create a folder to handle our infrastructure code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir ~/terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I like to follow this pattern when working with Terraform:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── modules
│ ├── networking
│ └── web
├── production
└── staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let’s create this folder structure&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/terraform
mkdir -p modules/{networking,web} production staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Our modules folder contains all the &lt;strong&gt;shared code&lt;/strong&gt; to create the pieces of the infrastructure (web servers, app servers, databases, vpc, etc). Each folder inside the modules folder is related to a specific module.&lt;br&gt;&lt;br&gt;
Next, I have folders for my environments (staging, production, qa, development, etc). Each of this folder contains code to use our shared modules and create a different architecture for each environment (This is my personal approach using Terraform, but feel free to work on a different way).&lt;/p&gt;
&lt;h4&gt;
  
  
  Our first module: Networking
&lt;/h4&gt;

&lt;p&gt;Let’s create our networking module. This will be responsible for creating the networking pieces of our infrastructure, like VPC, subnets, routing table, NAT server and the bastion instance.&lt;/p&gt;

&lt;p&gt;Before we get deep in the code, I wanna explain how terraform works:&lt;/p&gt;

&lt;p&gt;Terraform will provide us with some commands. Some of them are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;plan&lt;/strong&gt; : Displays all the changes that Terraform makes on our infrastructure&lt;br&gt;&lt;br&gt;
&lt;strong&gt;apply&lt;/strong&gt; : Executes all the changes to the infrastructure&lt;br&gt;&lt;br&gt;
&lt;strong&gt;destroy&lt;/strong&gt; : Destroys everything that was created with Terraform&lt;/p&gt;

&lt;p&gt;When you run Terraform inside a directory, it loads ALL .tf files from the directory and execute them (will not load on subfolders). Terraform will first create a graph of the resources to apply only in the final phase, so you don’t need to specify the resources in any specific order. The graph will determine the relations between the resources and ensure that Terraform creates they in the right order.&lt;/p&gt;
&lt;h4&gt;
  
  
  Continuing on our networking module
&lt;/h4&gt;

&lt;p&gt;Enter in the networking module&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd modules/networking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let’s create our first tf file. The one that specifies all variables needed for our module.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on the variables.tf file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;These are all variables that our networking module needs in order to create all resources. We need the CIDR for the VPC and the subnets, the AWS region that we will use, the key name and the environment that we are building.&lt;br&gt;&lt;br&gt;
This is the way that you specify a variable in Terraform&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "variable\_name" {
  description = "The description of the variable"
  default = "A default value if this isn't set
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ok. Now we have our variables.tf file to specify the interface of our module. Let’s create the file that will create networking stuffs for our module. Create the main.tf file in the networking folder. (you can specify any name that you want. Remember, all tf files will be loaded).&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on the main.tf file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Here we are creating all the networking part of our infrastructure based on the diagram that we saw. A VPC, both subnets (public and private), the Internet Gateway to the public subnet, the NAT server for the private subnet, the bastion host and all security group for the VPC, allowing inbound and outbound inside the VPC, and the security group for the bastion host, allowing the SSH on the Port 22. You can check &lt;a href="http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Scenario2.html" rel="noopener noreferrer"&gt;this link from AWS&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;This is how we create a resource on Terraform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "resource" "name" {
  attribute = "value"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The resource is the name of the resource that we want to build. Each cloud provider has different resources. For example, &lt;a href="https://www.terraform.io/docs/providers/aws/index.html" rel="noopener noreferrer"&gt;these resources&lt;/a&gt; from AWS. We can also concatenate values. Remember that we created the variables file? We use them here with the var.variable_name . Like in this part of the code, which we use the key_name variable that we specified in the variables file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws\_instance" "bastion" {
  ...
  key\_name = "${var.key\_name}"
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is how we created the instance and called its bastion. You can also get property of this resource in other parts of the code. Example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "someresource" "somename" {
  attribute = "${aws\_instance.bastion.id}"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We use the same idea of var concatenation. But we specify ${resource_type.resource_name.property} .&lt;/p&gt;

&lt;p&gt;Our networking module is almost ready. We need to output some variables after the module build the resources, so we can use it in other parts of the code. Terraform has the &lt;a href="https://www.terraform.io/intro/getting-started/outputs.html" rel="noopener noreferrer"&gt;output command,&lt;/a&gt; allowing us to expose variables.&lt;/p&gt;

&lt;p&gt;Create the output.tf file inside the networking folder.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch output.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on the output.tf file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This is how we output a variable from our module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "variable\_name" {
  value = "variable value"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will allow us to get these variables outside the module.&lt;/p&gt;
&lt;h4&gt;
  
  
  Using our module to build the networking from our environment
&lt;/h4&gt;

&lt;p&gt;Now that our networking module is ready, we can use it to build our networking from our environment (i.e, staging).&lt;br&gt;&lt;br&gt;
This is how our terraform folder looks now:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── modules
│ ├── networking
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ └── web
├── production
└── staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Go into staging folder.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/terraform/staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;First, create the public key for the staging servers&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -t rsa -C "staging\_key" -f ./staging\_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let’s create our main file. In this file, we will specify information of the AWS provider.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch \_main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;(We use _ at the beginning of the name because since terraform loads all files alphabetically, we need this to be loaded first, since it will create the keypair)&lt;/p&gt;

&lt;p&gt;You can specify things like Access and secret key in some ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specify it directly in the provider (not recommended)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region = "us-west-1"
  access\_key = "myaccesskey"
  secret\_key = "mysecretkey"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Using the AWS_ACCESS_KEY and AWS_SECRET_KEY environment variables
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ export AWS\_ACCESS\_KEY\_ID="myaccesskey"
$ export AWS\_SECRET\_ACCESS\_KEY="mysecretkey"

terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The second option is recommended because you don’t need to expose your secrets on the file. &lt;strong&gt;Bonus point&lt;/strong&gt; : If you have the &lt;a href="https://aws.amazon.com/pt/cli/" rel="noopener noreferrer"&gt;AWS cli&lt;/a&gt; you don’t need to export these variables. Only run the aws configure command and terraform will use the variables that you set on it.&lt;/p&gt;

&lt;p&gt;Insert the following content on the _main.tf file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;We will only specify the region to the provider. Both access and secret key, we will rely on the AWS cli .&lt;/p&gt;

&lt;p&gt;Now create the networking.tf file. It will use our module to create the resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch networking.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on the networking.tf file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This is how we use &lt;a href="https://www.google.com/url?sa=t&amp;amp;rct=j&amp;amp;q=&amp;amp;esrc=s&amp;amp;source=web&amp;amp;cd=1&amp;amp;cad=rja&amp;amp;uact=8&amp;amp;ved=0ahUKEwjYr97GxIHTAhVCkJAKHWi8AsQQFggaMAA&amp;amp;url=https%3A%2F%2Fwww.terraform.io%2Fdocs%2Fmodules%2Fusage.html&amp;amp;usg=AFQjCNHaKs5YpQe2IsNwXa9ikVF3N67CRw&amp;amp;bvm=bv.151426398,d.Y2I" rel="noopener noreferrer"&gt;modules on Terraform&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "name" {
  source = "location\_path"
  attribute = "value"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The module attributes are all variables that we specified before in the variables.tf file from our networking module. Look that we are passing more variables to our module attributes. We need our environment to require these variables too. Create the variables.tf file for our staging environment.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Add the following content to the variables.tf file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This file follows the same pattern of the module’s variables file. These are all variables that we need to build our staging networking piece.&lt;/p&gt;

&lt;p&gt;Ok… I think that we are ready to go. Your terraform’s folder structure should be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── modules
│ ├── networking
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ └── web
├── production
└── staging
    ├── \_main.tf
    ├── networking.tf
    └── variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run this command on staging folder: (Terraform’s commands should be run on the environments folder).&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/terraform/staging
terraform get
terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;*the terraform get command only syncs all modules.&lt;/p&gt;

&lt;p&gt;After executing the terraform plan command, it will ask you a lot of informations (our variables). Answer they:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F646%2F1%2ACKnvWOjniPmXHpbI8u6oNA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F646%2F1%2ACKnvWOjniPmXHpbI8u6oNA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will output a lot of things. Resources that will be created. You can analyze it to check if everything is ok. terraform plan will output only the planned change of the infrastructure.&lt;/p&gt;

&lt;p&gt;Now, let’s apply these modifications.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But it is asking for all that information again. To avoid this, you can follow 2 ways to automatically inject variable’s value to Terraform&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using a terraform.tfvars file and remember to ignore this file in your VCS.&lt;/li&gt;
&lt;li&gt;Specifying variables in Environment variables. TF_VAR_environment=staging terraform apply (this can be useful when you run through some CI tool)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will follow the first way. So, create a terraform.tfvars file&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch terraform.tfvars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on terraform.tfvars :&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Now you can run the apply command without being asked for variable’s value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should receive the following message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F868%2F1%2ALb4nvDoCX_hOyDckORfLOg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F868%2F1%2ALb4nvDoCX_hOyDckORfLOg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations..&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F500%2F1%2A6447MjaXTKhttJ5WlqTVpQ.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F500%2F1%2A6447MjaXTKhttJ5WlqTVpQ.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will generate 2 files on the staging folder: terraform.tfstate and terraform.tfstate.backup&lt;/p&gt;

&lt;p&gt;Terraform controls all their resources on this terraform.tfstate file. You should &lt;strong&gt;NEVER&lt;/strong&gt; delete this file. If you do, terraform will think that it needs to create new resources and will lose tracking with the others that it has been already created.&lt;/p&gt;

&lt;p&gt;And how can I make my team in sync with the state?&lt;/p&gt;

&lt;p&gt;You have 2 ways to keep the team with the remote in sync. You can commit this .tfstate file to your VCS repository, or use &lt;a href="https://www.terraform.io/docs/state/remote.html" rel="noopener noreferrer"&gt;Terraform Remote State&lt;/a&gt;. If you rely on terraform’s remote state, I really recommend you to use some wrapper tool for Terraform like &lt;a href="https://github.com/gruntwork-io/terragrunt" rel="noopener noreferrer"&gt;Terragrunt&lt;/a&gt;. It can handle the remote state locking and initializing it for you.&lt;/p&gt;
&lt;h4&gt;
  
  
  Creating the web servers
&lt;/h4&gt;

&lt;p&gt;This is our folder structure until now.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── modules
│ ├── networking
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ └── web
├── production
└── staging
    ├── \_main.tf
    ├── networking.tf
    ├── staging\_key
    ├── staging\_key.pub
    ├── terraform.tfstate
    ├── terraform.tfstate.backup
    ├── terraform.tfvars
    └── variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Go into our web module&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/terrform/modules/web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Following the same flow we used to create the networking module, let’s create our variables file to specify the interface to our module:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on variables.tf :&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Create the main.tf file to handle the creation of the resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on main.tf :&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This file is practically the same from our networking’s main.tf, we are only creating different resources.&lt;br&gt;&lt;br&gt;
Some differences that you can note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In this example we are using the count attribute. It specifies to Terraform, to create N times this resource. If we pass 5 on the value, it will create 5 instances.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws\_instance" "web" {
  count = "${var.web\_instance\_count}"
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and you can create dynamic names with the counting number using the count.index property, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tags {
  Name = "web-server-${count.index + 1}"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;We are passing a file to the user_data property. In order to execute some code on the instance initialization, we need to pass the user_data attribute to our instance and we are specifying a file.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user\_data = "${file("${path.module}/files/user\_data.sh")}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will load the content of the user_data.sh file and pass to the attribute.&lt;/p&gt;

&lt;p&gt;But we haven’t created this file yet, let’s do it. On the web module folder, create the files folder and the user_data.sh file.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir files
touch files/user\_data.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on the files/user_data.sh :&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This will install the nginx when the instance is created.&lt;/p&gt;

&lt;p&gt;Now, let’s create the output.tf file to get the load balancer’s DNS after the execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch output.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on the output.tf :&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Our web module is done.&lt;/p&gt;

&lt;p&gt;This is how our terraform structure is now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── modules
│ ├── networking
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ └── web
│ ├── files
│ │ └── user\_data.sh
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
├── production
└── staging
    ├── \_main.tf
    ├── networking.tf
    ├── staging\_key
    ├── staging\_key.pub
    ├── terraform.tfstate
    ├── terraform.tfstate.backup
    ├── terraform.tfvars
    └── variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Using our web module
&lt;/h4&gt;

&lt;p&gt;Let’s back to our staging folder&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/terraform
cd staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, we will use our recent created web module. Create a web.tf file&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch web.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Insert the following content on the web.tf :&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This is pretty much the same way we used in our networking module. We are using almost the same variables that we already specified (except for web_instance_count . And some variables, we pass the output from our networking module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"${module.networking.public\_subnet\_id}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This way we get the public_subnet_id output created on the networking module.&lt;/p&gt;

&lt;p&gt;Let’s add the web_instance_count variable to our variables.tf file and terraform.tfvars . This variable represents the number of web instances that we will be created.&lt;/p&gt;

&lt;p&gt;Your variables.tf from staging folder should be like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;And your terraform.tfvars should be like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Let’s create an output.tf for our staging environment. With this, we can get the ELB hostname from our web module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch output.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Add the following content to output.tf :&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This is the final directory structure that we have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── modules
│ ├── networking
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ └── web
│ ├── files
│ │ └── user\_data.sh
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
├── production
└── staging
    ├── \_main.tf
    ├── networking.tf
    ├── output.tf
    ├── staging\_key
    ├── staging\_key.pub
    ├── terraform.tfstate
    ├── terraform.tfstate.backup
    ├── terraform.tfvars
    ├── variables.tf
    └── web.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now run terraform plan to check which resources will be created. (before, run terraform get to update the modules)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform get
terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F724%2F1%2A_3j03t04L3BbcAM9B3t5Tg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F724%2F1%2A_3j03t04L3BbcAM9B3t5Tg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Done. Terraform will create 5 new resources from our web module.&lt;/p&gt;

&lt;p&gt;Let’s apply it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F840%2F1%2AzojlSrGMXN93qiHdoZVPhQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F840%2F1%2AzojlSrGMXN93qiHdoZVPhQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yay.. our instances was created. Let’s get our Load Balancer DNS and try to open it on a Browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[terraform](http://%24(terraform) output elb\_hostname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will return the DNS from our Load Balancer. Open it on a browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F756%2F1%2AKAFGuGGUKWTWISGD2dqmBQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F756%2F1%2AKAFGuGGUKWTWISGD2dqmBQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AX4llUCSctCV1RBczcOPusw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AX4llUCSctCV1RBczcOPusw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s working!!! With this, you finished the creation of your infrastructure.&lt;/p&gt;

&lt;h4&gt;
  
  
  Final Notes:
&lt;/h4&gt;

&lt;p&gt;Your web instances don’t have public ips:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ASipX4XhQdT5_Z0zYySLmNg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ASipX4XhQdT5_Z0zYySLmNg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to SSH then, you need to use the bastion host created on the networking module.&lt;/p&gt;

&lt;p&gt;Get the bastion host public IP,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AHctNFDW39zAagy7iRyiirQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AHctNFDW39zAagy7iRyiirQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and SSH on it with the -A flag to enable agent forwarding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod 400 staging\_key.pub

ssh-add -K staging\_key

ssh -A ubuntu@52.53.227.241
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, inside the Bastion host, you can connect into your web server private IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh ubuntu@10.0.2.113
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AjxxGXtybRz43Rw47BacdxA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AjxxGXtybRz43Rw47BacdxA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, destroy everything&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get the full code of the example &lt;a href="https://github.com/duduribeiro/terraform_example" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F499%2F1%2APlGYD6UUSY3wbNzGRAkHSw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F499%2F1%2APlGYD6UUSY3wbNzGRAkHSw.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cheers,&lt;/p&gt;

&lt;p&gt;🍻&lt;/p&gt;




</description>
      <category>terraform</category>
      <category>devops</category>
      <category>infrastructureasco</category>
      <category>aws</category>
    </item>
    <item>
      <title>Connecting on RDS Server that is not publicly accessible</title>
      <dc:creator>Cadu Ribeiro</dc:creator>
      <pubDate>Tue, 10 Jan 2017 23:23:39 +0000</pubDate>
      <link>https://dev.to/caduribeiro/connecting-on-rds-server-that-is-not-publicly-accessible-392k</link>
      <guid>https://dev.to/caduribeiro/connecting-on-rds-server-that-is-not-publicly-accessible-392k</guid>
      <description>&lt;p&gt;Let’s imagine the following scenario:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F600%2F1%2AhywIXPeJfZtsmJylpYPhNw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F600%2F1%2AhywIXPeJfZtsmJylpYPhNw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You have web servers on a public subnet that you can connect and your RDS instance is hosted on a private subnet. This way, your database instance is not publicly accessible through the internet and you can’t connect your local client with it.&lt;/p&gt;

&lt;p&gt;It’s not possible to do a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -u user -p -h RDS\_HOST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To establish a connection with the database, you’ll need to use your public EC2 instances to act as a bridge to the RDS. Let’s make a SSH Tunnel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i /path/to/keypair.pem -NL 9000:RDS\_ENDPOINT:3306 ec2-user@EC2\_HOST -v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;-i /path/to/keypair.pem :&lt;/strong&gt; The &lt;em&gt;-i&lt;/em&gt; option will inform the ssh which key will be used to connect. If you already added your key with &lt;em&gt;ssh-add&lt;/em&gt;, this is not necessary.
-NL — N will not open a session with the server. It will set up the tunnel. L will set up the port forwarding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-NL : N&lt;/strong&gt; will not open a session with the server. It will set up the tunnel. L will set up the port forwarding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9000:RDS_ENDPOINT:3306 :&lt;/strong&gt; The &lt;em&gt;-L&lt;/em&gt; option will make the port forwarding based on this argument. The first number &lt;em&gt;9000&lt;/em&gt; is the local port that you want to use to connect with the remote host. &lt;em&gt;RDS_ENDPOINT&lt;/em&gt; is the RDS host of your database instance. &lt;em&gt;3306&lt;/em&gt; is the port of the remote host that you want to access (3306 is the MySQL’s default port).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ec2-user@EC2_HOST :&lt;/strong&gt; How ssh your public EC2 instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-v :&lt;/strong&gt; Is optional. With this you will print the ssh log on your terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this you can now connect to your private RDS instance using your local client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -h 127.0.0.1 -P9000 -u RDS\_USER -p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your EC2 instance is on a private subnet too, you will need to set up a bastion host to make the bridge possible. Bastion host is an instance that will be placed on a public subnet and will be accessible using SSH. You will use the same SSH tunnel, only changing the host used to point the bastion host.&lt;/p&gt;

&lt;p&gt;Cheers 🍻&lt;/p&gt;

</description>
      <category>cloudcomputing</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
