<?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: Alexandre Couedelo</title>
    <description>The latest articles on DEV Community by Alexandre Couedelo (@xnok).</description>
    <link>https://dev.to/xnok</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%2F616848%2F8651b781-465a-4293-a3cf-fc324c880a28.jpeg</url>
      <title>DEV Community: Alexandre Couedelo</title>
      <link>https://dev.to/xnok</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xnok"/>
    <language>en</language>
    <item>
      <title>How to stay informed about recent trends in tech?</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Tue, 12 Jul 2022 14:03:37 +0000</pubDate>
      <link>https://dev.to/xnok/how-to-stay-informed-about-recent-trends-in-tech-eo4</link>
      <guid>https://dev.to/xnok/how-to-stay-informed-about-recent-trends-in-tech-eo4</guid>
      <description>&lt;p&gt;&lt;a href="https://medium.com/geekculture/how-to-stay-informed-about-recent-trends-in-tech-8cdc2ca81df7?source=rss-6e21f0d290d1------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mWdV0tke--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2600/0%2ASy5bslhUVliZ5GlC" alt="" width="880" height="613"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use knowledge to your advantage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/geekculture/how-to-stay-informed-about-recent-trends-in-tech-8cdc2ca81df7?source=rss-6e21f0d290d1------2"&gt;Continue reading on Geek Culture »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>interview</category>
      <category>programming</category>
      <category>knowledge</category>
    </item>
    <item>
      <title>How to create Ansible Inventory with Terraform?</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Thu, 26 May 2022 10:43:44 +0000</pubDate>
      <link>https://dev.to/xnok/how-to-create-ansible-inventory-with-terraform-46b5</link>
      <guid>https://dev.to/xnok/how-to-create-ansible-inventory-with-terraform-46b5</guid>
      <description>&lt;p&gt;&lt;a href="https://faun.pub/how-to-create-ansible-inventory-with-terraform-a32fcbe11be6?source=rss-6e21f0d290d1------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lQY0F424--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/902/1%2Aq3hBzwv18My0sjrr8nYliw.png" alt="" width="880" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform is the go tool to provision your infrastructure and all the VM you need to run your applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://faun.pub/how-to-create-ansible-inventory-with-terraform-a32fcbe11be6?source=rss-6e21f0d290d1------2"&gt;Continue reading on FAUN Publication »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>technology</category>
      <category>devops</category>
      <category>github</category>
      <category>terraform</category>
    </item>
    <item>
      <title>How to Create SSH keys with Terraform?</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Tue, 17 May 2022 08:07:20 +0000</pubDate>
      <link>https://dev.to/xnok/how-to-create-ssh-keys-with-terraform-59j3</link>
      <guid>https://dev.to/xnok/how-to-create-ssh-keys-with-terraform-59j3</guid>
      <description>&lt;p&gt;&lt;a href="https://faun.pub/how-to-create-ssh-keys-with-terraform-a615dfc631c1?source=rss-6e21f0d290d1------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KW6eVgdu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/902/0%2AAwoi_2ICuXm2yOY1.png" alt="" width="880" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform allows you to set up servers in the cloud without much hassle. To access those servers with your CI/CD tools, however, requires…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://faun.pub/how-to-create-ssh-keys-with-terraform-a615dfc631c1?source=rss-6e21f0d290d1------2"&gt;Continue reading on FAUN Publication »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>terraform</category>
      <category>devops</category>
      <category>technology</category>
    </item>
    <item>
      <title>How to Configure GitHub Environments with Terraform?</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Wed, 04 May 2022 20:49:10 +0000</pubDate>
      <link>https://dev.to/xnok/how-to-configure-github-environments-with-terraform-2njg</link>
      <guid>https://dev.to/xnok/how-to-configure-github-environments-with-terraform-2njg</guid>
      <description>&lt;p&gt;&lt;a href="https://faun.pub/how-to-configure-github-environments-with-terraform-d2b76766547b?source=rss-6e21f0d290d1------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ENdyKS_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/902/1%2AAQ3-knCHXNJYTqsijexnIw.png" alt="" width="880" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did you know that GitHub Actions supports the definition of environments?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://faun.pub/how-to-configure-github-environments-with-terraform-d2b76766547b?source=rss-6e21f0d290d1------2"&gt;Continue reading on FAUN Publication »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>technology</category>
      <category>github</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>What is Terraform Cloud and why you might need it?</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Sat, 12 Mar 2022 18:15:28 +0000</pubDate>
      <link>https://dev.to/xnok/what-is-terraform-cloud-and-why-you-might-need-it-4g5i</link>
      <guid>https://dev.to/xnok/what-is-terraform-cloud-and-why-you-might-need-it-4g5i</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;a href="https://cloud.hashicorp.com/products/terraform"&gt;Terraform Cloud&lt;/a&gt;&lt;/strong&gt; is the managed solution offered by &lt;a href="https://www.hashicorp.com/"&gt;&lt;strong&gt;Hashicorp&lt;/strong&gt;&lt;/a&gt; to run &lt;strong&gt;Terraform&lt;/strong&gt; configurations 💡. It is in some manner a “CI/CD tool” to manage Infrastructure as Code (IaC). It eliminates the need for you to configure a dedicated task in your CI/CD (Jenkins, Bamboo, Circle CI, etc.) and also provided a tailor-made UI, remote state management, and private module registry.&lt;/p&gt;

&lt;p&gt;While it is always possible to use terraform as a CLI in any CI/CD this always comes with frustration when it comes to creating the perfect workflow to plan and apply changes to your infrastructure. If you work a lot with those tools Terraform and Ansible you will realize that dedicated tools add a lot of value.&lt;/p&gt;

&lt;p&gt;Terraform Cloud is I deal if you are working on your personal project as it is Free. I also like to use it as it offers a central UI for all your project and a &lt;a href="https://www.terraform.io/language/state/remote"&gt;state-management&lt;/a&gt;. Otherwise, you have to create a resource with your cloud provider to manage your state S3 for AWS, Cloud Storage for GCP, Blob Storage for Azure. I believe you won’t find the same effortless management of Terraform scripts with another provider so Terraform Cloud is the best place to get started with Terraform.&lt;/p&gt;

&lt;p&gt;In this article, I will give you a tour of Terraform Cloud and the necessary explanations to get started. This piece is part of a series I created on How to create infrastructure for a small self-hosted project. You can find all the code here: &lt;a href="https://github.com/xNok/infra-bootstrap-tools"&gt;https://github.com/xNok/infra-bootstrap-tools&lt;/a&gt;, but this article does not how much prior requirements if it is not being a little bit familiar with Terraform.&lt;/p&gt;

&lt;h1&gt;
  
  
  Register on Terraform Cloud.
&lt;/h1&gt;

&lt;p&gt;Simple task! Just go to &lt;a href="https://cloud.hashicorp.com/products/terraform"&gt;https://cloud.hashicorp.com/products/terraform&lt;/a&gt; and click on &lt;code&gt;Try cloud for free&lt;/code&gt; , complete the form, validate your email and let's meet at the next step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ooIMD22C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yo7ooxvs5rqq1t1wpjl6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ooIMD22C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yo7ooxvs5rqq1t1wpjl6.png" alt="Register to Terraform Cloud" width="880" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Workspace in Terraform Cloud.
&lt;/h2&gt;

&lt;p&gt;Once you registered it is time to create your first workspace. &lt;a href="https://www.terraform.io/language/state/workspaces"&gt;Terraform workspace&lt;/a&gt; is a feature design to manage &lt;code&gt;environments&lt;/code&gt; (aka. different setup using the same underlying terraform logic). For instance, you could have a workspace &lt;code&gt;dev&lt;/code&gt; and a workspace &lt;code&gt;prod&lt;/code&gt;. However, you can use workspaces how you’d like. Here is how to create a workspace. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h5fZ1hX1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3408zu8rlxelkwy66ec.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h5fZ1hX1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3408zu8rlxelkwy66ec.gif" alt="Create workspace" width="880" height="377"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Here are the steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on &lt;code&gt;create a new workspace&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Name your workspace (choose a naming convention to keep things in order)&lt;/li&gt;
&lt;li&gt;(Optional) define the &lt;code&gt;Terraform working directory&lt;/code&gt; this is the folder holding the &lt;a href="http://main.tf"&gt;main.tf&lt;/a&gt; you wish to execute.&lt;/li&gt;
&lt;li&gt;Create the workspace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraform Cloud free remote backend option is a game changer. You can configure as follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="s"&gt;"remote"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"app.terraform.io"&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nokwebspace"&lt;/span&gt;

    &lt;span class="n"&gt;workspaces&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"infra-bootstrap-tools-digitalocean"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is considered best practice to place the remote backend configuration in a file called &lt;code&gt;backend.tf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you need to log in to your Terraform cloud using an API key. First, run the command and follow the instruction from the prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;terraform&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create an API key using the UI. The small GIF below will show you how.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NuCb3c1N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nthrwlk3954fmob0nu8k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NuCb3c1N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nthrwlk3954fmob0nu8k.gif" alt="tf login" width="880" height="377"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Create a small example
&lt;/h2&gt;

&lt;p&gt;I was working on &lt;a href="https://www.digitalocean.com/"&gt;DigitalOcean&lt;/a&gt; for my &lt;a href="https://github.com/xNok/infra-bootstrap-tools"&gt;https://github.com/xNok/infra-bootstrap-tools&lt;/a&gt; tutorials, so this example uses digital Ocean Terraform provider, but do not worry nothing fancy at all simple create a “project” (some kinda folder for resources).&lt;/p&gt;

&lt;p&gt;Like in any terraform project after creating the &lt;code&gt;[backend.tf](http://backend.tf)&lt;/code&gt; I create a &lt;code&gt;[versions.tf](http://versions.tf)&lt;/code&gt; to configure my provider&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;versions.tf&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Simple this configuration comes straight from the provider documentation: 💪&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;digitalocean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"digitalocean/digitalocean"&lt;/span&gt;
      &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.17.1"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="s"&gt;"digitalocean"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Configuration options
&lt;/span&gt;  &lt;span class="c1"&gt;# will use DIGITALOCEAN_ACCESS_TOKEN env variable
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;[backend.tf](http://backend.tf)&lt;/code&gt; done ☑️, &lt;code&gt;[versions.tf](http://versions.tf)&lt;/code&gt; done ✅, next &lt;code&gt;main.tf&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;main.tf&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now you are going to start writing some terraform code. Nothing difficult and this cost nothing, just a test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;/**&lt;/span&gt;
 &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="c1"&gt;# DigitalOcean Project
&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
 &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Projects&lt;/span&gt; &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;organize&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;DigitalOcean&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt; 
 &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;like&lt;/span&gt; &lt;span class="n"&gt;Droplets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Spaces&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt; &lt;span class="n"&gt;balancers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
 &lt;span class="o"&gt;*/&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s"&gt;"digitalocean_project"&lt;/span&gt; &lt;span class="s"&gt;"infra-bootstrap-tools"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"infra-bootstrap-tools"&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Startup infra for small self-hosted project"&lt;/span&gt;
  &lt;span class="n"&gt;purpose&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"IoT"&lt;/span&gt;
  &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Development"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Validating the Script locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;terraform&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note when running this script locally you will need to set &lt;code&gt;DIGITALOCEAN_ACCESS_TOKEN&lt;/code&gt; with your DigitalOcean API key&lt;/p&gt;

&lt;h2&gt;
  
  
  Provisioning the infra with Terraform Cloud
&lt;/h2&gt;

&lt;p&gt;This is where the show starts 🎇. The last setup required is to set &lt;code&gt;DIGITALOCEAN_ACCESS_TOKEN&lt;/code&gt; in terraform cloud. Terraform Cloud recently added the notion of &lt;a href="https://learn.hashicorp.com/tutorials/terraform/cloud-multiple-variable-sets?in=terraform/cloud"&gt;variable set&lt;/a&gt; that I found amazing to manage account API credentials. With a &lt;strong&gt;&lt;em&gt;variable set,&lt;/em&gt;&lt;/strong&gt; I can define all my DigitalOcean related variable (API key, organization name, admin email, etc.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zuokm397sadg1jxxh65a.png"&gt;Variable Set&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have set your credentials simply commit and push your code to GitHub. If you go back to &lt;code&gt;workspaces&lt;/code&gt; you see the status updated to planned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AI8ijxFb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6evwrsms2m8bu3jgiur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AI8ijxFb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6evwrsms2m8bu3jgiur.png" alt="Terraform workspace planed" width="880" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, inspect your workspace, look at the UI. You will notice that each resource gets a separate view to give you the time to carefully inspect your plan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WxNVpeSu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kidya7zg2q94jy05zvqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WxNVpeSu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kidya7zg2q94jy05zvqh.png" alt="Terraform plan" width="880" height="815"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your review is done you can go ahead and hit &lt;code&gt;confirm and Push&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y4py3-G0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mro6yteckmiknxcmvh1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y4py3-G0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mro6yteckmiknxcmvh1l.png" alt="Confirm and Push plan" width="880" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There you go new resource created with Terraform Cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6tcsm44is3rhx687l7i0.png"&gt;Terraform apply completed&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you should do next?
&lt;/h2&gt;

&lt;p&gt;Explore the added feature of Terraform Cloud&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Done working for the day on your project? Launch a destroy plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fGZ_5gKv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5d8e4i43kkfguaiwvdch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fGZ_5gKv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5d8e4i43kkfguaiwvdch.png" alt="Queue destroy plan" width="880" height="851"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add notifications to your Slack Channels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W0siadGz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5it6yvrqgocy2nfwla0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W0siadGz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5it6yvrqgocy2nfwla0.png" alt="Slack Integration" width="880" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex workflow with &lt;strong&gt;Run Triggers&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IVGSBjdA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhlcc1v5atj04dx0hw1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IVGSBjdA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhlcc1v5atj04dx0hw1a.png" alt="Workflow Run Trigger" width="880" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run plan on pull-requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Uf2OlJC---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3i4qxbfd2q88ahasvv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Uf2OlJC---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3i4qxbfd2q88ahasvv5.png" alt="Speculative plan on pull-requests" width="880" height="785"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The Free offering of Terraform Cloud offers all you need for an open-source project (remote state and plan/apply workflow) so I don’t see any reason not to take advantage of this opportunity 😇.&lt;/p&gt;

&lt;p&gt;For corporate usage is a very good product made by the creators of Terraform. If don’t have anything better already then just hop on.&lt;/p&gt;

&lt;p&gt;I will use Terraform Cloud in my personal projects. I am building this repository &lt;a href="https://github.com/xNok/infra-bootstrap-tools"&gt;https://github.com/xNok/infra-bootstrap-tools&lt;/a&gt; to demonstrate how to build a minimal (but scalable) infrastructure for a simple self-hosted application. I am sharing the setup I have been using for over many years and writing articles about all the DevOps-related knowledge. So have a look and follow along 🤟.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Experiment with Vagrant and Ansible - Docker Swarm for Small Self-hosted Projects</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Thu, 24 Feb 2022 20:44:36 +0000</pubDate>
      <link>https://dev.to/xnok/docker-swarm-is-still-relevant-for-small-self-hosted-projects-experiment-with-vagrant-and-ansible-1l7g</link>
      <guid>https://dev.to/xnok/docker-swarm-is-still-relevant-for-small-self-hosted-projects-experiment-with-vagrant-and-ansible-1l7g</guid>
      <description>&lt;p&gt;Experimenting with Docker Swarm and having only a single node is a bit sad 😞. Luckily in my previous tutorial, you learn how to create *&lt;strong&gt;&lt;em&gt;&lt;a href="https://dev.to/xnok/a-disposable-local-test-environment-is-essential-for-devops-sysadmin-1jhk"&gt;A Disposable Local Test Environment using Vagrant and Ansible&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt;* If you followed along you know a little bit more about Vagrant and Ansible *&lt;strong&gt;&lt;em&gt;but nothing worth showing off&lt;/em&gt;&lt;/strong&gt;* 🤯*&lt;strong&gt;&lt;em&gt;,&lt;/em&gt;&lt;/strong&gt;* so let up our game and create a multi-VM Docker Swarm cluster. &lt;/p&gt;

&lt;p&gt;This involves using &lt;strong&gt;Vagrant&lt;/strong&gt; to create multiple VM, then using &lt;strong&gt;Ansible&lt;/strong&gt; to install docker on each machine, before creating a Docker Swarm cluster with all our nodes. On this is in place you have a solid foundation to experiment with Docker  &lt;/p&gt;

&lt;p&gt;I want to remind you that the goal of this tutorial series is to document what I consider the bare minimum for a small self-hosted side project. I invite you to visit my repository for more information: &lt;a href="https://github.com/xNok/infra-bootstrap-tools" rel="noopener noreferrer"&gt;https://github.com/xNok/infra-bootstrap-tools&lt;/a&gt;. At this point, we are doing the groundwork of setting up a server to host the application we will deploy later as docker containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provisioning Multiple VMs with Vagrant
&lt;/h2&gt;

&lt;p&gt;Like in the previous tutorial we use Vagrant to create virtual machines. The difference is that this time we are provisioning 3 VMs so the file became sensibly bigger. I will explain the content of this file in the next section.&lt;/p&gt;

&lt;p&gt;Here is the new updated &lt;code&gt;Vagrantfile&lt;/code&gt; . Before running &lt;code&gt;vagrant up&lt;/code&gt; there are two more things you need to set up: &lt;code&gt;ansible.cfg&lt;/code&gt; and &lt;code&gt;inventory&lt;/code&gt; The code will be below this big &lt;code&gt;Vagrantfile&lt;/code&gt; .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# -*- mode: ruby -*-&lt;/span&gt;
&lt;span class="c1"&gt;# vi: set ft=ruby :&lt;/span&gt;

&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# Every Vagrant development environment requires a box. You can search for&lt;/span&gt;
  &lt;span class="c1"&gt;# boxes at https://vagrantcloud.com/search.&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"generic/ubuntu2004"&lt;/span&gt;

  &lt;span class="c1"&gt;# We are moving to a more complex example so to avoid issues we will limit the RAM of each VM&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;linked_clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;#########&lt;/span&gt;
  &lt;span class="c1"&gt;# Nodes: host our apps &lt;/span&gt;
  &lt;span class="c1"&gt;#########&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"node1"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"172.17.177.21"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"node2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"172.17.177.22"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;#########&lt;/span&gt;
  &lt;span class="c1"&gt;# Controller: host our tools&lt;/span&gt;
  &lt;span class="c1"&gt;#########&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s1"&gt;'controller'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="c1"&gt;# The Ansible Local provisioner requires that all the Ansible Playbook files are available on the guest machine&lt;/span&gt;
    &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synced_folder&lt;/span&gt; &lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;owner: &lt;/span&gt;&lt;span class="s2"&gt;"vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;group: &lt;/span&gt;&lt;span class="s2"&gt;"vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mount_options: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dmode=755,fmode=600"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# /!\ This is only usefull because the tutorial files are under .articles/xyz&lt;/span&gt;
    &lt;span class="c1"&gt;# otherwise Ansible would get the roles from the root folder&lt;/span&gt;
    &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synced_folder&lt;/span&gt; &lt;span class="s2"&gt;"../../roles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/vagrant/roles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;owner: &lt;/span&gt;&lt;span class="s2"&gt;"vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;group: &lt;/span&gt;&lt;span class="s2"&gt;"vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mount_options: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dmode=755,fmode=600"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"172.17.177.11"&lt;/span&gt;

    &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"ansible_local"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# ansible setup&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;install&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;install_mode&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pip_args_only"&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playbook&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"playbook.yml"&lt;/span&gt;
      &lt;span class="c1"&gt;# ansible.version = "2.10.7"&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pip_install_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sudo apt-get install -y python3-pip python-is-python3 haveged &amp;amp;&amp;amp; sudo ln -s -f /usr/bin/pip3 /usr/bin/pip"&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pip_args&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ansible==2.10.7"&lt;/span&gt;
      &lt;span class="c1"&gt;# provsionning&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playbook&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"playbook.yml"&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt; &lt;span class="c1"&gt;# or only "nodes" group, etc.&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventory_path&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"inventory"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the two last things you need 😅. Create an &lt;code&gt;ansible.cfg&lt;/code&gt; file, we are fine-tuning ansible configuration to work with our setup. You won’t have an interactive shell so we won’t be able to accept SSH fingerprints. This configuration will also be essential to have ansible your working in your CI/CD since we are facing the same constraint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;host_key_checking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ssh_connection&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;ssh_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="no"&gt;ControlMaster&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="no"&gt;ControlPersist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="no"&gt;UserKnownHostsFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sr"&gt;/dev/nu&lt;/span&gt;&lt;span class="n"&gt;ll&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="no"&gt;IdentitiesOnly&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last we need to manually define the inventory file. Since we selected the IPs in the private network this is a simple task. Not that we also take advantage of our &lt;code&gt;synced_folder&lt;/code&gt; to obtain the SSH keys required for ansible to connect to &lt;code&gt;node1&lt;/code&gt; and &lt;code&gt;node2&lt;/code&gt; .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;node1&lt;/span&gt;      &lt;span class="n"&gt;ansible_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;172.17&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;177.21&lt;/span&gt; &lt;span class="n"&gt;ansible_ssh_private_key_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sr"&gt;/vagrant/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vagrant&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;machines&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;virtualbox&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt;
&lt;span class="n"&gt;node2&lt;/span&gt;      &lt;span class="n"&gt;ansible_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;172.17&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;177.22&lt;/span&gt; &lt;span class="n"&gt;ansible_ssh_private_key_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sr"&gt;/vagrant/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vagrant&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;machines&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;virtualbox&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt;
&lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="n"&gt;ansible_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;172.17&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;177.11&lt;/span&gt; &lt;span class="n"&gt;ansible_connection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;managers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;controller&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can provision the infra with Vagrant&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Focus on the Vagrantfile
&lt;/h2&gt;

&lt;p&gt;First, we select the Vagrant box we use as a base. This time I use ubuntu instead (&lt;code&gt;generic/ubuntu2004&lt;/code&gt; ) I found it easier for installing the latest version of Ansible on the controller. Notice that I added &lt;code&gt;virtualbox&lt;/code&gt; specific configurations. Since you are running multiples VMs it is important to control the size of each VM as to not starve your PC resources. Also, I used the  &lt;code&gt;linked_clone&lt;/code&gt; option to speed up the process, that way VirtualBox will create a &lt;code&gt;base&lt;/code&gt; VM (that will stay turned off) and clone this VM to create the other three.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Every Vagrant development environment requires a box. You can search for&lt;/span&gt;
  &lt;span class="c1"&gt;# boxes at https://vagrantcloud.com/search.&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"generic/ubuntu2004"&lt;/span&gt;

  &lt;span class="c1"&gt;# We are moving to a more complex example so to avoid issues we will limit the RAM of each VM&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;linked_clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we have the two worker node definition. This step is straightforward. What is new here is that we set fix IPs to our VM, this makes it easier to create a static Ansible inventory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="c1"&gt;#########&lt;/span&gt;
  &lt;span class="c1"&gt;# Nodes: host our apps &lt;/span&gt;
  &lt;span class="c1"&gt;#########&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"node1"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"172.17.177.21"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"node2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"172.17.177.22"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before starting with the controller I want you to look at the Vagrant documentation and notice that there is two Ansible provider &lt;strong&gt;&lt;code&gt;ansible&lt;/code&gt; and &lt;code&gt;ansible_local&lt;/code&gt; .&lt;/strong&gt; I used the second one so I don't have to bother &lt;strong&gt;installing ansible&lt;/strong&gt; and I find that this approach is closer to the CI/CD approach you will use later in the series. As a result, to create two nodes we will provision three machines one of which is the controller and has the responsibility of running ansible and provisioning the other machines.&lt;/p&gt;

&lt;p&gt;First, we create two &lt;code&gt;synced_folder&lt;/code&gt; to give the VM access to our playbook and roles. That way we can update any Ansible code and use it immediately in the VM. Note that to avoid permission issues I forced the &lt;code&gt;uid&lt;/code&gt; and &lt;code&gt;guid&lt;/code&gt; as well as restricting files read/write access to the user only. The reason is that Ansible uses SSH keys stored in this folder (see inventory file) and permission for those keys needs to be that way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="c1"&gt;#########&lt;/span&gt;
  &lt;span class="c1"&gt;# Controller: host our tools&lt;/span&gt;
  &lt;span class="c1"&gt;#########&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s1"&gt;'controller'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="c1"&gt;# The Ansible Local provisioner requires that all the Ansible Playbook files are available on the guest machine&lt;/span&gt;
    &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synced_folder&lt;/span&gt; &lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;owner: &lt;/span&gt;&lt;span class="s2"&gt;"vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;group: &lt;/span&gt;&lt;span class="s2"&gt;"vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mount_options: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dmode=755,fmode=600"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# /!\ This is only usefull because the tutorial files are under .articles/xyz&lt;/span&gt;
    &lt;span class="c1"&gt;# otherwise Ansible would get the roles from the root folder&lt;/span&gt;
    &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synced_folder&lt;/span&gt; &lt;span class="s2"&gt;"../../roles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/vagrant/roles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;owner: &lt;/span&gt;&lt;span class="s2"&gt;"vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;group: &lt;/span&gt;&lt;span class="s2"&gt;"vagrant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mount_options: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dmode=755,fmode=600"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"172.17.177.11"&lt;/span&gt;

    &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"ansible_local"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# ansible setup&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;install&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;install_mode&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pip_args_only"&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playbook&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"playbook.yml"&lt;/span&gt;
      &lt;span class="c1"&gt;# ansible.version = "2.10.7"&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pip_install_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sudo apt-get install -y python3-pip python-is-python3 haveged &amp;amp;&amp;amp; sudo ln -s -f /usr/bin/pip3 /usr/bin/pip"&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pip_args&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ansible==2.10.7"&lt;/span&gt;
      &lt;span class="c1"&gt;# provsionning&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playbook&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"playbook.yml"&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt; &lt;span class="c1"&gt;# or only "nodes" group, etc.&lt;/span&gt;
      &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventory_path&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"inventory"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more complicated part comes in the &lt;code&gt;provision&lt;/code&gt; section. I want to use the latest 2.x version of ansible to use the latest version of &lt;code&gt;docker_swarm&lt;/code&gt; and &lt;code&gt;docker_swarm_info&lt;/code&gt; modules. The issue is that ansible made a lot of structural changes between 2.7 and 2.10. So a little bit of hacking is required to install the desired version. I found this method on Github and it works like a charm. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Docker with Ansible
&lt;/h2&gt;

&lt;p&gt;Our playbook is about to become a little bit more complicated on top of that installing docker is something you may want to reuse in several projects. I will assume you are somewhat familiar with Ansible and took the time to play a little bit with the hello-world playbook you used in the first tutorial.&lt;/p&gt;

&lt;p&gt;There are multiple ways to create roles with ansibles but I want to keep is as simple as possible. But you should know that the recommended way to create roles is to use &lt;code&gt;ansible-galaxy init&lt;/code&gt; . See the documentation &lt;a href="https://galaxy.ansible.com/docs/contributing/creating_role.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The downside of this approach is that it creates a folder and files you may not use. Let’s keep things simple and create the minimal structure.&lt;/p&gt;

&lt;p&gt;Ansible looks for a folder called &lt;code&gt;roles&lt;/code&gt; and then a subfolder with the name of that role here &lt;code&gt;docker&lt;/code&gt; , finally, the first thing Ansible does is to read the &lt;code&gt;main.yml&lt;/code&gt; from the &lt;code&gt;meta&lt;/code&gt; folder of that role to get collect metadata information about it.&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; roles/docker/meta
&lt;span class="nb"&gt;touch &lt;/span&gt;roles/docker/meta/main.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;meta/main.yml&lt;/code&gt; only requires you to specify dependencies for this role, meaning other roles that you would expect to execute before this one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
  &lt;span class="c1"&gt;# List your role dependencies here, one per line. Be sure to remove the '[]' above,&lt;/span&gt;
  &lt;span class="c1"&gt;# if you add dependencies to this list.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we need to defines so tasks to complete the docker installation. It is a good practice exercise to look at the official docker installation documentation and turn it into an Ansible role: &lt;a href="https://docs.docker.com/install/linux/docker-ce/debian/" rel="noopener noreferrer"&gt;https://docs.docker.com/install/linux/docker-ce/debian/&lt;/a&gt;. Create the file &lt;code&gt;/tasks/main.yaml&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; roles/docker/tasks
&lt;span class="nb"&gt;touch &lt;/span&gt;roles/docker/tasks/main.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the content of &lt;code&gt;main.yml&lt;/code&gt; should look along those lines:&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="c"&gt;#################################################&lt;/span&gt;
&lt;span class="c"&gt;# OR INFRA Role: Docker&lt;/span&gt;
&lt;span class="c"&gt;# Source: https://docs.docker.com/install/linux/docker-ce/debian/&lt;/span&gt;
&lt;span class="c"&gt;#################################################&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="c"&gt;###&lt;/span&gt;
&lt;span class="c"&gt;# GENERAL Setup&lt;/span&gt;
&lt;span class="c"&gt;###&lt;/span&gt;
- name: Install required system packages
  apt: &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;={{&lt;/span&gt; item &lt;span class="o"&gt;}}&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="nv"&gt;update_cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes
  &lt;/span&gt;loop: &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'apt-transport-https'&lt;/span&gt;, &lt;span class="s1"&gt;'ca-certificates'&lt;/span&gt;, &lt;span class="s1"&gt;'software-properties-common'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

- name: Add Docker GPG apt Key
  apt_key:
    url: https://download.docker.com/linux/debian/gpg
    state: present

- name: Add Docker Repository
  apt_repository:
    repo: deb &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64] https://download.docker.com/linux/&lt;span class="o"&gt;{{&lt;/span&gt;ansible_distribution | lower &lt;span class="o"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;{{&lt;/span&gt;ansible_distribution_release&lt;span class="o"&gt;}}&lt;/span&gt; stable
    state: present

- name: Update apt and &lt;span class="nb"&gt;install &lt;/span&gt;docker-ce
  apt: &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;={{&lt;/span&gt; item &lt;span class="o"&gt;}}&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="nv"&gt;update_cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes
  &lt;/span&gt;loop: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'docker-ce'&lt;/span&gt;, &lt;span class="s1"&gt;'docker-ce-cli'&lt;/span&gt;, &lt;span class="s1"&gt;'docker-compose'&lt;/span&gt;, &lt;span class="s1"&gt;'containerd.io'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

- name: Ensure docker &lt;span class="nb"&gt;users &lt;/span&gt;are added to the docker group.
  user:
    name: &lt;span class="s2"&gt;"{{ item }}"&lt;/span&gt;
    &lt;span class="nb"&gt;groups&lt;/span&gt;: docker
    append: &lt;span class="nb"&gt;true
  &lt;/span&gt;with_items: &lt;span class="o"&gt;[&lt;/span&gt;vagrant, ubuntu]

- name: Start docker
  service:
    name: docker
    state: started
    enabled: &lt;span class="nb"&gt;yes&lt;/span&gt;

&lt;span class="c"&gt;########&lt;/span&gt;
&lt;span class="c"&gt;# Testing Setup&lt;/span&gt;
&lt;span class="c"&gt;# Pull, start, stop a hello-world container&lt;/span&gt;
&lt;span class="c"&gt;########&lt;/span&gt;
- name: Pull default Docker image &lt;span class="k"&gt;for &lt;/span&gt;testing
  docker_image:
    name: &lt;span class="s2"&gt;"hello-world"&lt;/span&gt;
    &lt;span class="nb"&gt;source&lt;/span&gt;: pull

- name: Create default containers
  docker_container:
    name: &lt;span class="s2"&gt;"hello-world"&lt;/span&gt;
    image: &lt;span class="s2"&gt;"hello-world"&lt;/span&gt;
    state: present

- name: Stop a container
  docker_container:
    name: &lt;span class="s2"&gt;"hello-world"&lt;/span&gt;
    state: stopped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your &lt;code&gt;playbook.yml&lt;/code&gt; file to specify that we want to use this role against all our VMs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;- name: This is a hello-world example
  hosts: all

  roles:
  - docker

  tasks:
    - name: Create a file called &lt;span class="s1"&gt;'/tmp/testfile.txt'&lt;/span&gt; with the content &lt;span class="s1"&gt;'hello world'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
      copy:
        content: hello-world
        dest: /tmp/testfile.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it is time to run Vagrant&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the provisioning is completed you should have three VMs with docker setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Docker Swarm with Ansible
&lt;/h2&gt;

&lt;p&gt;To complete our setup we will need to create three more roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker-swarm-controller&lt;/code&gt; will install the required python package on the host running Ansible to controller and manager the swarm. This includes notably the python docker package.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-swarm-manager&lt;/code&gt; will initialize the swam and join all the targeted nodes as manager&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-swarm-node&lt;/code&gt; will join all the targeted nodes as workers nodes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the final Ansible playbook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;This&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="n"&gt;requirement&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;
  &lt;span class="ss"&gt;hosts: &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;

  &lt;span class="ss"&gt;roles:
  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;become: &lt;/span&gt;&lt;span class="n"&gt;yes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;This&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="no"&gt;Docker&lt;/span&gt; &lt;span class="no"&gt;Swarm&lt;/span&gt; &lt;span class="no"&gt;Manager&lt;/span&gt;
  &lt;span class="ss"&gt;hosts: &lt;/span&gt;&lt;span class="n"&gt;managers&lt;/span&gt;

  &lt;span class="ss"&gt;roles:
  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;swarm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;become: &lt;/span&gt;&lt;span class="n"&gt;yes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# this role is for the host running Ansible to manager the swarm&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;swarm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;become: &lt;/span&gt;&lt;span class="n"&gt;yes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="c1"&gt;# this role is for creating the swarm and adding host as manager&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;This&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;join&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;swarm&lt;/span&gt;
  &lt;span class="ss"&gt;hosts: &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;

  &lt;span class="ss"&gt;roles:
  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;swarm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="c1"&gt;# this role is for the host to join the swarm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;docker-swarm-controller&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This role is straightforward I don’t think I need to comment on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;# OR INFRA Role: Docker Swarm Controller&lt;/span&gt;
&lt;span class="c1"&gt;# Machines running ansible need some special python package&lt;/span&gt;
&lt;span class="c1"&gt;# Source: &lt;/span&gt;
&lt;span class="c1"&gt;#    https://github.com/arillso/ansible.traefik&lt;/span&gt;
&lt;span class="c1"&gt;#    https://geek-cookbook.funkypenguin.co.nz/ha-docker-swarm/traefik/&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# GENERAL Setup&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Install&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="nb"&gt;system&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;
  &lt;span class="ss"&gt;apt: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt; &lt;span class="n"&gt;update_cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yes&lt;/span&gt;
  &lt;span class="ss"&gt;loop: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'python3-pip'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'virtualenv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'python3-setuptools'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Install&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;stuff&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="ss"&gt;pip:
      executable: &lt;/span&gt;&lt;span class="n"&gt;pip3&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;jsondiff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;passlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;docker-swarm-manager&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You need to be careful here you can only init a docker swarm once. As a convention, the first node of the group &lt;code&gt;managers&lt;/code&gt; will be used as the founder of the swarm. Notice that this role uses a variable &lt;code&gt;swarm_managers_inventory_group_name&lt;/code&gt; . I like my variables to be verbose 😂. We need to read facts about our nodes, this variable tells us what group in the inventory is used for managers&lt;/p&gt;

&lt;p&gt;You may be wondering what &lt;code&gt;hostvars[groups[swarm_managers_inventory_group_name][0]].result.swarm_facts.JoinTokens.Manager&lt;/code&gt; do? When Ansible executed &lt;code&gt;Init a new swarm with default parameters&lt;/code&gt; we asked Ansible to register some information with &lt;code&gt;register: result&lt;/code&gt; this is simply the path to collect the information about the join token that the other nodes need to join the swarm as a manager. &lt;code&gt;Get join-token for manager nodes&lt;/code&gt; effectively persisted the join token on each of the managers as a fact. More about Ansible facts and Variables &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;# OR INFRA Role: Docker Swarm Manager&lt;/span&gt;
&lt;span class="c1"&gt;# Source: &lt;/span&gt;
&lt;span class="c1"&gt;#    https://github.com/arillso/ansible.traefik&lt;/span&gt;
&lt;span class="c1"&gt;#    https://geek-cookbook.funkypenguin.co.nz/ha-docker-swarm/traefik/&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# GENERAL Setup&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# SWARM Setup&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Init&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;swarm&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;
  &lt;span class="ss"&gt;docker_swarm:
    state: &lt;/span&gt;&lt;span class="n"&gt;present&lt;/span&gt;
    &lt;span class="ss"&gt;advertise_addr: &lt;/span&gt;&lt;span class="s2"&gt;"{{ ansible_host }}"&lt;/span&gt;
  &lt;span class="ss"&gt;register: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
  &lt;span class="ss"&gt;when: &lt;/span&gt;&lt;span class="n"&gt;inventory_hostname&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;swarm_managers_inventory_group_name&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# only on the first manager&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Manager Setup&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Get&lt;/span&gt; &lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;
  &lt;span class="ss"&gt;set_fact:
    join_token_manager: &lt;/span&gt;&lt;span class="s2"&gt;"{{ hostvars[groups[swarm_managers_inventory_group_name][0]].result.swarm_facts.JoinTokens.Manager }}"&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Join&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="n"&gt;managers&lt;/span&gt;
  &lt;span class="ss"&gt;docker_swarm:
    state: &lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;
    &lt;span class="ss"&gt;join_token: &lt;/span&gt;&lt;span class="s2"&gt;"{{ join_token_manager }}"&lt;/span&gt;
    &lt;span class="ss"&gt;advertise_addr: &lt;/span&gt;&lt;span class="s2"&gt;"{{ ansible_host }}"&lt;/span&gt;
    &lt;span class="ss"&gt;remote_addrs: &lt;/span&gt;&lt;span class="s2"&gt;"{{ groups[swarm_managers_inventory_group_name] | map('extract', hostvars, ['ansible_host']) | join(',') }}"&lt;/span&gt;
  &lt;span class="ss"&gt;when: &lt;/span&gt;&lt;span class="n"&gt;inventory_hostname&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;swarm_managers_inventory_group_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# exclude the first manager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;docker-swarm-node&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This role is very similar to the previous one except that this time we get the join worker token and register our node as workers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;# OR INFRA Role: Docker Swarm Node&lt;/span&gt;
&lt;span class="c1"&gt;# Source: &lt;/span&gt;
&lt;span class="c1"&gt;#    https://github.com/arillso/ansible.traefik&lt;/span&gt;
&lt;span class="c1"&gt;#    https://geek-cookbook.funkypenguin.co.nz/ha-docker-swarm/traefik/&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# GENERAL Setup&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Get&lt;/span&gt; &lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;
  &lt;span class="ss"&gt;set_fact:
    join_token_worker: &lt;/span&gt;&lt;span class="s2"&gt;"{{ hostvars[groups[swarm_managers_inventory_group_name][0]].result.swarm_facts.JoinTokens.Worker }}"&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Add Nodes&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;
  &lt;span class="ss"&gt;docker_swarm:
    state: &lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;
    &lt;span class="ss"&gt;advertise_addr: &lt;/span&gt;&lt;span class="s2"&gt;"{{ ansible_host }}"&lt;/span&gt;
    &lt;span class="ss"&gt;join_token: &lt;/span&gt;&lt;span class="s2"&gt;"{{ join_token_worker }}"&lt;/span&gt;
    &lt;span class="ss"&gt;remote_addrs: &lt;/span&gt;&lt;span class="s2"&gt;"{{ groups[swarm_managers_inventory_group_name] | map('extract', hostvars, ['ansible_host']) | join(',') }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing that the Docker Swarm is working
&lt;/h2&gt;

&lt;p&gt;Let’s see if everything looks ok in our cluster. SSH to the controller node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant ssh controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the command &lt;code&gt;docker node ls&lt;/code&gt;  to see your cluster&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant@ubuntu2004:~&lt;span class="nv"&gt;$ &lt;/span&gt;docker node &lt;span class="nb"&gt;ls
&lt;/span&gt;ID                            HOSTNAME                 STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
odumha179h5qbtln5jfoql9xc &lt;span class="k"&gt;*&lt;/span&gt;   ubuntu2004.localdomain   Ready     Active         Leader           20.10.12
opeigd4zdccyzam3yjaakdfzk     ubuntu2004.localdomain   Ready     Active                          20.10.12
yjy282nbmzcr5gx90rvvacla2     ubuntu2004.localdomain   Ready     Active                          20.10.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Quickly setting up VMs and creating Ansible roles is the fastest way for me to test a simple setup at no cost. This is why Vagrant and Ansible make such a great team to create a &lt;strong&gt;Disposable Local Test Environment&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;As of now, your Docker Swarm is totally empty. In future tutorials let's create a simple stack you can reuse for almost all your projects. You can check my Github repository &lt;a href="https://github.com/xNok/infra-bootstrap-tools" rel="noopener noreferrer"&gt;https://github.com/xNok/infra-bootstrap-tools&lt;/a&gt; to find more tutorials and build the following infrastructure.&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%2Fgithub.com%2FxNok%2Finfra-bootstrap-tools%2Fblob%2Fmain%2Fdiagrams%2Fscaled_up_infra_for_small_self_hosted_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%2Fgithub.com%2FxNok%2Finfra-bootstrap-tools%2Fblob%2Fmain%2Fdiagrams%2Fscaled_up_infra_for_small_self_hosted_project.png" alt="Infrastructure for small self-hosted project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolving common problems
&lt;/h2&gt;

&lt;p&gt;Sometimes when provisioning multiple machine issues occur. You should not restart everything from ground zero but use the power of Ansible and Vagrant to resume operation from where the problem occurred.&lt;/p&gt;

&lt;p&gt;When the provisioning fails (ansible error) you can restart the provisioning with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant provision controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It happened to me that an error occurred to a node (SSH errors or node unreachable) in that case reload only the node that creates problems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant reload node1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/geerlingguy/ansible-role-docker" rel="noopener noreferrer"&gt;https://github.com/geerlingguy/ansible-role-docker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ruanbekker/ansible-docker-swarm" rel="noopener noreferrer"&gt;https://github.com/ruanbekker/ansible-docker-swarm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/atosatto/ansible-dockerswarm" rel="noopener noreferrer"&gt;https://github.com/atosatto/ansible-dockerswarm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/58232506/docker-swarm-module-join-token-parameter-for-ansible-not-working" rel="noopener noreferrer"&gt;Docker_swarm module - join_token parameter for ansible not working&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ansible</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A Disposable Local Test Environment is Essential for DevOps / SysAdmin</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Tue, 15 Feb 2022 22:26:19 +0000</pubDate>
      <link>https://dev.to/xnok/a-disposable-local-test-environment-is-essential-for-devops-sysadmin-1jhk</link>
      <guid>https://dev.to/xnok/a-disposable-local-test-environment-is-essential-for-devops-sysadmin-1jhk</guid>
      <description>&lt;p&gt;Working on your infrastructure setups and testing your automation script can be tedious if you don’t have a proper test environment that enables a fast iteration loop. The best when automating your server provisioning with &lt;a href="https://www.ansible.com/" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt; scripts is to be able to set up servers a local server step by step then play all the steps from the ground up and make sure everything goes as planned. Once the local and iterative design phase is complete you will be ready to create an automated pipeline to set up an actual production environment.&lt;/p&gt;

&lt;p&gt;Why Ansible you may ask? Ansible is simple and easy to learn if you have some Linux administration knowledge. We are configuring a Linux server after all you can’t escape that part.&lt;/p&gt;

&lt;p&gt;With a small project spread across several articles, I want to show you what I consider the minimum requirement for a small self-hosted project. I invite you to check my &lt;a href="https://github.com/xNok/infra-bootstrap-tools" rel="noopener noreferrer"&gt;Github repository&lt;/a&gt; for other articles and more details about the whole project.&lt;/p&gt;

&lt;p&gt;In this article, you will see how to get started and set up a local testing environment. You will learn about &lt;a href="https://www.vagrantup.com/" rel="noopener noreferrer"&gt;Vagrant&lt;/a&gt; a tool to create local virtual machines on the fly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Vagrant
&lt;/h2&gt;

&lt;p&gt;First, install Vagrant. Simply go to the &lt;a href="https://www.vagrantup.com/" rel="noopener noreferrer"&gt;main page&lt;/a&gt; and download the product for your platform. Once Vagrant is installed you can need a hypervisor, the most popular is VirtualBox because it can run on Linux, Windows, and Mac OS. With both requirements installed (Vagrant + VirtualBox) you can get started spawning a Linux Debian ****virtual machine and start experimenting.&lt;/p&gt;

&lt;p&gt;To create a VM, you need to understand the Concept of a &lt;a href="https://www.vagrantup.com/docs/boxes" rel="noopener noreferrer"&gt;Vagrant Box&lt;/a&gt;. Put simply Boxes is the way Vagrant packages environments so they can be shared via a registry such as &lt;a href="https://app.vagrantup.com/boxes/search" rel="noopener noreferrer"&gt;Vagrant Cloud&lt;/a&gt;. Let’s search for a Debian box that fit your need. This one should do the job: &lt;a href="https://app.vagrantup.com/generic/boxes/debian10" rel="noopener noreferrer"&gt;generic/debian10&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two steps to getting your box running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant init generic/debian10
vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first command created a file called &lt;a href="https://www.vagrantup.com/docs/vagrantfile" rel="noopener noreferrer"&gt;Vagrantfile&lt;/a&gt; that describes how to provision your environment. The file created with the &lt;code&gt;init&lt;/code&gt; command simply specifies that we want to use the Vagrant box &lt;strong&gt;&lt;a href="https://app.vagrantup.com/generic/boxes/debian10" rel="noopener noreferrer"&gt;generic/debian10&lt;/a&gt;&lt;/strong&gt; as a base.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# Every Vagrant development environment requires a box. You can search for&lt;/span&gt;
  &lt;span class="c1"&gt;# boxes at https://vagrantcloud.com/search.&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"generic/debian10"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command &lt;code&gt;vagrant up&lt;/code&gt; starts and provision the VM. Once your box is running you can SSH into and play with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that point, you could start making procedures about how to install everything you need. But this approach would no be to be maintainable and reproducible. This is why you need to also learn about Vagrant &lt;a href="https://www.vagrantup.com/docs/provisioning" rel="noopener noreferrer"&gt;provisioner&lt;/a&gt;s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vagrant and Ansible
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ansible/ansible" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt; is a simple automation tool that can orchestrate pretty much any task you would need to configure and maintain your infrastructure. Ansible work best when you are dealing with actual machines (servers or virtual machines), I would not recommend using it to provision Cloud infrastructure as it lacks a state management capabilities you can find in other tools such as &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;. But here we will be dealing with configuring Linux servers so ansible works best here.&lt;/p&gt;

&lt;p&gt;In order to make the bridge between Vagrant and Ansible, you will use the &lt;a href="https://www.vagrantup.com/docs/provisioning/ansible_common" rel="noopener noreferrer"&gt;Ansible *Provisioners&lt;/a&gt;.* Provisioners are tools that vagrant will use to set up your virtual machine and Vagrant support the most command tools on the market and offers the ability to create your own provisioner to meet your needs. But let’s focus on the &lt;a href="https://www.vagrantup.com/docs/provisioning/ansible_local" rel="noopener noreferrer"&gt;Ansible Local Provisioner&lt;/a&gt;, that way you don’t even have to worry about installing Ansible on your local machine Vagrant will install it inside your virtual machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# Every Vagrant development environment requires a box. You can search for&lt;/span&gt;
  &lt;span class="c1"&gt;# boxes at https://vagrantcloud.com/search.&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"generic/debian10"&lt;/span&gt;

    &lt;span class="c1"&gt;# The Ansible Local provisioner requires that all the Ansible Playbook files are available on the guest machine&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synced_folder&lt;/span&gt; &lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/vagrant"&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"ansible_local"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playbook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"playbook.yml"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vagrant makes it easy for you to run your ansible provisioning scripts against the virtual machine it created for you. You should see a folder &lt;code&gt;.vagrant&lt;/code&gt; at the root of your project (this folder should be ignored by git, if not add it to your .&lt;code&gt;gitignore&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Next you need to create an “hello-world” playbook called &lt;code&gt;playbook.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;This is a hello-world example&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a file called '/tmp/testfile.txt' with the content 'hello world'.&lt;/span&gt;
      &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/tmp/testfilprovisioning&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are ready to try out your first ansible provisioning with Vagrant&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;Vagrant up
Bringing machine &lt;span class="s1"&gt;'default'&lt;/span&gt; up with &lt;span class="s1"&gt;'virtualbox'&lt;/span&gt; provider...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Checking &lt;span class="k"&gt;if &lt;/span&gt;box &lt;span class="s1"&gt;'generic/debian10'&lt;/span&gt; version &lt;span class="s1"&gt;'3.6.8'&lt;/span&gt; is up to date...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Clearing any previously &lt;span class="nb"&gt;set &lt;/span&gt;forwarded ports...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Fixed port collision &lt;span class="k"&gt;for &lt;/span&gt;22 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 2222. Now on port 2200.
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Clearing any previously &lt;span class="nb"&gt;set &lt;/span&gt;network interfaces...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Forwarding ports...
    default: 22 &lt;span class="o"&gt;(&lt;/span&gt;guest&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 2200 &lt;span class="o"&gt;(&lt;/span&gt;host&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;adapter 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Running &lt;span class="s1"&gt;'pre-boot'&lt;/span&gt; VM customizations...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Booting VM...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Waiting &lt;span class="k"&gt;for &lt;/span&gt;machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2200
    default: SSH username: vagrant
    default: SSH auth method: private key
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Machine booted and ready!
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Checking &lt;span class="k"&gt;for &lt;/span&gt;guest additions &lt;span class="k"&gt;in &lt;/span&gt;VM...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Mounting shared folders...
    default: /vagrant &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; E:/Nokwebspace/infra-bootstrap-tools
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; default: Running provisioner: ansible_local...
    default: Installing Ansible...
    default: Running ansible-playbook...

PLAY &lt;span class="o"&gt;[&lt;/span&gt;This is a hello-world example] &lt;span class="k"&gt;*******************************************&lt;/span&gt;

TASK &lt;span class="o"&gt;[&lt;/span&gt;Gathering Facts] &lt;span class="k"&gt;*********************************************************&lt;/span&gt;
ok: &lt;span class="o"&gt;[&lt;/span&gt;default]

TASK &lt;span class="o"&gt;[&lt;/span&gt;Create a file called &lt;span class="s1"&gt;'/tmp/testfile.txt'&lt;/span&gt; with the content &lt;span class="s1"&gt;'hello world'&lt;/span&gt;.] &lt;span class="k"&gt;***&lt;/span&gt;
changed: &lt;span class="o"&gt;[&lt;/span&gt;default]

PLAY RECAP &lt;span class="k"&gt;*********************************************************************&lt;/span&gt;
default                    : &lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2    &lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1    &lt;span class="nv"&gt;unreachable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you make changes to the playbook you can simply run only the provisioning stage&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant provision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you are done or want to take a break simply stop the VM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant halt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You are ready to start working on your next infrastructure provisioning project. Having a disposable test environment is essential and Vagrant will soon become your favorite tool. &lt;/p&gt;

&lt;p&gt;You can check my Github repository &lt;a href="https://github.com/xNok/infra-bootstrap-tools" rel="noopener noreferrer"&gt;https://github.com/xNok/infra-bootstrap-tools&lt;/a&gt; to find more tutorials and build the following infrastructure.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5yzndivuhwcijsc8isxu.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5yzndivuhwcijsc8isxu.png" alt="Infrastructure for small self-hosted projects"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ansible</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Build a Custom Split Mechanical Keyboard: Bill of material</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Mon, 31 Jan 2022 05:01:09 +0000</pubDate>
      <link>https://dev.to/xnok/how-to-build-a-custom-split-mechanical-keyboard-bill-of-material-3e57</link>
      <guid>https://dev.to/xnok/how-to-build-a-custom-split-mechanical-keyboard-bill-of-material-3e57</guid>
      <description>&lt;p&gt;&lt;a href="https://couedeloalexandre.medium.com/how-to-build-a-custom-split-mechanical-keyboard-bill-of-material-b2cf1ab62fef?source=rss-6e21f0d290d1------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4-cPR259--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2600/1%2A8jdcm-tGHWxuKKx28-jzJw.jpeg" alt="" width="880" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One Step further In the world of Open-Source Ergo Split Keyboards&lt;/p&gt;

&lt;p&gt;&lt;a href="https://couedeloalexandre.medium.com/how-to-build-a-custom-split-mechanical-keyboard-bill-of-material-b2cf1ab62fef?source=rss-6e21f0d290d1------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>diy</category>
      <category>inspiration</category>
      <category>ideas</category>
      <category>technology</category>
    </item>
    <item>
      <title>Multi-Cloud is NOT the solution to the next AWS outage.</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Mon, 03 Jan 2022 12:04:49 +0000</pubDate>
      <link>https://dev.to/xnok/multi-cloud-is-not-the-solution-to-the-next-aws-outage-jnh</link>
      <guid>https://dev.to/xnok/multi-cloud-is-not-the-solution-to-the-next-aws-outage-jnh</guid>
      <description>&lt;p&gt;&lt;a href="https://faun.pub/multi-cloud-is-not-the-solution-to-the-next-aws-outage-bb41c0b14573?source=rss-6e21f0d290d1------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XDSxAb2b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2600/0%2Av3yLF0diiOcQIKaH" alt="" width="880" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many things you can do first in terms of disaster recovery.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://faun.pub/multi-cloud-is-not-the-solution-to-the-next-aws-outage-bb41c0b14573?source=rss-6e21f0d290d1------2"&gt;Continue reading on FAUN Publication »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>leadership</category>
      <category>softwaredevelopment</category>
      <category>technology</category>
      <category>devops</category>
    </item>
    <item>
      <title>A Split Mechanical Keyboard, You Can Build Yourself!</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Thu, 23 Dec 2021 17:06:21 +0000</pubDate>
      <link>https://dev.to/xnok/a-split-mechanical-keyboard-you-can-build-yourself-5dj1</link>
      <guid>https://dev.to/xnok/a-split-mechanical-keyboard-you-can-build-yourself-5dj1</guid>
      <description>&lt;h3&gt;
  
  
  A Split Mechanical Keyboard, You Can Build Yourself! 🎅
&lt;/h3&gt;

&lt;h4&gt;
  
  
  In the world of Open-Source Ergo Split Keyboards
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ss86lK-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A7aSsyC4mGrKZoyTKNshWEg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ss86lK-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A7aSsyC4mGrKZoyTKNshWEg.jpeg" alt="Ferries Sweep Keyboard" width="880" height="770"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Ferris split keyboard courtesy of Pierre Viseu Chevalier&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since I started working from home, I have been obsessed with creating the most ergonomic workplace for myself. Standing desks, ergonomic stools, etc.&lt;/p&gt;

&lt;p&gt;But up until now, nothing I could reasonably build myself. 😢&lt;/p&gt;

&lt;p&gt;But a month ago, everything changed. I read about the benefit of split keyboards. A Keyboard cut halfway through in the middle prevents your &lt;strong&gt;wrists&lt;/strong&gt; from tilting inward and relaxing your arms and &lt;strong&gt;shoulders&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I discovered a massive community of passionate makers: &lt;a href="https://www.reddit.com/r/ErgoMechKeyboards/"&gt;r/ErgoMechKeyboards&lt;/a&gt; , many videos ,and Github repositories with exciting designs to copy and do yourself. ✨&lt;/p&gt;

&lt;p&gt;In this Story, I have summarized all the knowledge I have gathered so far and will walk you through creating your Keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  What skills do you need to create one of these beauties?
&lt;/h3&gt;

&lt;p&gt;Quite a lot, actually! It should range from electronic to 3D modeling to soldering and buying stuff. On top of that, it is not an expensive project; it should range from $50 to $150. But if you are passionate or curious about engineering in general, it is an excellent first project as it is accessible by anyone.&lt;/p&gt;

&lt;p&gt;The main activities are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Designing and Printing a PCB (Printed Circuit Board)&lt;/li&gt;
&lt;li&gt;Buying and Assembling the electronic components&lt;/li&gt;
&lt;li&gt;Designing and manufacturing the casing to protect the electronic and look event cooler 😎.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  I) Designing and Printing a PCB
&lt;/h3&gt;

&lt;p&gt;This is what a PCB looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t_wR2049--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AVi1dnT2Q9foPWHCyf3KZNw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t_wR2049--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AVi1dnT2Q9foPWHCyf3KZNw.jpeg" alt="PCB keyboard" width="880" height="368"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Ferries Sweep PCB courtesy of Pierre Viseu Chevalier, David Barr, Ibnu Daru Aji, Duccio&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To create a keyboard, you essentially have two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1) You create a case and mechanically attach the key switches to the case and solder cables between the switches.&lt;/li&gt;
&lt;li&gt;2) Print a PCB and solder the keys and other components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are several advantages to the second approach. First soldering is much easier; you just have to solder key switches in the sockets created on the board. The board itself is strong enough to hold the keys and make the Keyboard usable without a case. As a result, you can create an ultra-light keyboard and save you some effort in the process of making it for a few $.&lt;/p&gt;

&lt;h4&gt;
  
  
  I.1) Choosing the layout
&lt;/h4&gt;

&lt;p&gt;The first step is choosing how your Keyboard will look like. How many keys? The spacing between the keys, etc.&lt;/p&gt;

&lt;p&gt;Compare existing split keyboard layouts &lt;a href="https://jhelvy.shinyapps.io/splitkbcompare/"&gt;here&lt;/a&gt;. As a first step, see what is available and print the layout to get a real feel of the Keyboard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☑️ Screen and Print keybord layouts from: &lt;a href="https://jhelvy.shinyapps.io/splitkbcompare/"&gt;https://jhelvy.shinyapps.io/splitkbcompare/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PtLUjNz2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A17BFGr9WiW8Y1nvFT3USvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PtLUjNz2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A17BFGr9WiW8Y1nvFT3USvg.png" alt="Me printing the layout of a Corne" width="880" height="357"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Me printing the layout of a Corne&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'd like you to reflect on the fact that you are most likely only frequently using half of the keys on your Keyboard. A standard keyboard usually has just above 100 keys. You may have noticed that most of those keyboards rarely have more than 50 keys, and this is perfectly fine.&lt;/p&gt;

&lt;p&gt;People using split Keyboard type the same way you type on your phone keyboard: At first, you only have the letters, then you press the "?123" symbol so you can type number and some of the symbols; then, if you want more symbols you type the "=&amp;lt;" key.&lt;/p&gt;

&lt;p&gt;Ultimately &lt;strong&gt;you will program the layout of your Keyboard&lt;/strong&gt; so you will be able to do whatever you want.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️Not all layout on &lt;a href="https://jhelvy.shinyapps.io/splitkbcompare/"&gt;https://jhelvy.shinyapps.io/splitkbcompare/&lt;/a&gt; are open source, so you won’t be able to get the file for printing the PCB&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  I.2) Designing the PCB
&lt;/h4&gt;

&lt;p&gt;Once you have set yourself to mind a layout, you can customize it. Search on Google or on Github for the source to get started. Theoretically, you could just download the file and send it to the PCB manufacturing. But why tunning your chance to have a genuinely unique keyboard?&lt;/p&gt;

&lt;p&gt;I learned the basics about how to customize a PCB from &lt;a href="https://www.youtube.com/watch?v=JqpBKuEVinw"&gt;this video&lt;/a&gt;. I highly recommend watching it. But here is a quick summary of what you need.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☑️ You will need a software to edit the PCB such as KiCad: &lt;a href="https://www.youtube.com/redirect?event=video_description&amp;amp;redir_token=QUFFLUhqbjN0LU4zQnBtaFA3NDBoVVF1Z3ZOeFpaUkZLd3xBQ3Jtc0ttZEl1Vi1rMlZsWEtXYlR6NWZFb2VfM3ZzY0NFUXpIQlR2YXJhVlVzbmN4MkVVaWh0XzdSdk91OTlESzg0MU9Rd0swQlRrblhqRWQ2X2R2RFJNS3ZyelkzbFVaczd1cU5FOVg1TVp3eERHcXEtZXczUQ&amp;amp;q=https%3A%2F%2Fwww.kicad.org"&gt;https://www.kicad.org&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the sake of this example, I downloaded the PCB &lt;a href="https://github.com/davidphilipbarr/Sweep/blob/main/Sweepv2.1/sweepv2.1_gerber.zip"&gt;Gerber File for Sweepv2.1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N8LTJ_pl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A3o4kTnLEslnYjEGoNQvQLA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N8LTJ_pl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A3o4kTnLEslnYjEGoNQvQLA.png" alt="Sweepv2.1 in KiCad Gerber Viewer" width="880" height="467"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Sweepv2.1 in KiCad Gerber Viewer&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❗️️️️❗️ ️️️️️If you want to have a quike look to a Gerber file archive you can use this &lt;a href="https://www.pcbway.com/project/OnlineGerberViewer.html"&gt;Online Gerber Viewer&lt;/a&gt; (I found it much clearer to understand the different layers)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PCB manufacturers literally use Gerber Files to print your order layer bay layer. See the different layers on the right side. In the archive, you open containers with one file per layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DlIt6wM0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/822/1%2AmP2dnuHRW3W8kjxBFVK6NA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DlIt6wM0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/822/1%2AmP2dnuHRW3W8kjxBFVK6NA.png" alt="Gerber files" width="822" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;*.xln&lt;/strong&gt; is the Drill layer, = Where to make holes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;*.gm1&lt;/strong&gt; the board Edges = how to cut the PCB to detached from the mainboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;*B_Cu.gbl&lt;/strong&gt; and &lt;strong&gt;*F_Cu.gtl&lt;/strong&gt; are the front and back copper layer = where to print copper wire to create connectivity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;*B_Mask.gts&lt;/strong&gt; and &lt;strong&gt;*F_Mask.gbs&lt;/strong&gt; are the back and front solder masks = was to print the protective layer on top of the copper layer. This layer is ofter green and gives there distinctive color to the PCB. Luckily more colors are available 😉.&lt;/li&gt;
&lt;li&gt;\ &lt;em&gt;**B_Silk.gbo&lt;/em&gt;* and \ &lt;em&gt;**F_Silk.gto&lt;/em&gt;* are the back and front silkscreen = the decorative prints on top of the solder masks, usually used to write references dor the component to solder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Well, this is not entirely true. Once you understand each layer's use, nothing stops you from customizing a PCB to your heart content 👼. Before editing the board, you need to know what component you want to fit in, and this is the object of the next section 😈.&lt;/p&gt;

&lt;h3&gt;
  
  
  II) Choosing the components
&lt;/h3&gt;

&lt;h4&gt;
  
  
  II.1) Choosing the switches
&lt;/h4&gt;

&lt;p&gt;The first thing you need to know is that there are essentially three types of mechanical switches.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clicky switches&lt;/strong&gt; are the noisy ones. The typical switches you associate with a mechanical keyboard give you this satisfying click sound at the end of a stroke.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tactile switches&lt;/strong&gt; are not as noisy but give you tactile feedback when you press the key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linear switches&lt;/strong&gt; are more and more common and provide no feedback or noise as you type, design for a smooth and soothing typing experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tubtLDOV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/600/1%2AqepnoBqBUnrL_EwAto94tw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tubtLDOV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/600/1%2AqepnoBqBUnrL_EwAto94tw.jpeg" alt="Types of mechanical switches" width="600" height="295"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Types of mechanical switches&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Second the nuance you can find high vs. low profile. This choice is often associated with choosing Cherry MX VS. Kailh Chocolate.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❗️️️️ ❗️️️️ T️here is more brand of key switches I invite you to read more about it &lt;a href="https://keyboardclack.com/mechanical-keyboard-switches-comparison-guide-colors-types-brands/"&gt;here&lt;/a&gt;. Most manufacturer provided both type of profile, but i notice that in Open-source built Kailh was choosen for their low profile key and Cherry MX for their high profile key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hICyQCO3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/640/1%2AV2KhnpfWV1UPWAS5geWMsA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hICyQCO3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/640/1%2AV2KhnpfWV1UPWAS5geWMsA.jpeg" alt="igh Profile vs. Low Profile" width="640" height="562"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;High Profile vs. Low Profile courtesy of u/songcq&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The choice should be influenced by the feeling you are looking for in your Keyboard and whether you want your Keyboard to be compact or not. For a compact Keyboard, you need to go for low-profile switches.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☑️ You will need to select you switches and the download the specs (datasheet). This Github repository contains datasheets for most manufacturer: &lt;a href="https://github.com/keyboardio/keyswitch_documentation"&gt;https://github.com/keyboardio/keyswitch_documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You need to validate that the key switches you are selecting are compatible with your PCB. Otherwise, you will need to do some edits to make sure they properly fit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rQIke-0k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1003/1%2A8cvpm3BpIMI2YIMJuDYDmQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rQIke-0k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1003/1%2A8cvpm3BpIMI2YIMJuDYDmQ.png" alt="Circuit Board Layout for Keith Choc Blu" width="880" height="642"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Circuit Board Layout for Keith Choc Blue&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  II.2) Choosing the keycaps
&lt;/h4&gt;

&lt;p&gt;The only thing that matters is selecting key switches that match the brand you choose (Cherry MX VS. Kailh Chocolate). Aside from that, just have fun and explore the many options available on the market.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N5_fVb06--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AyK1mt0NP8cwpsvxfwu6CPQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N5_fVb06--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AyK1mt0NP8cwpsvxfwu6CPQ.jpeg" alt="Fun Keycaps for Mechanical Keyboard from Etsy" width="880" height="660"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Fun Keycaps for Mechanical Keyboard from Etsy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you want to push this project a bit further, you can decide to print your own keycap.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☑️ You can screen for existing 3D model with online libraries like : &lt;a href="https://www.yeggi.com/"&gt;https://www.yeggi.com/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CRXqPXBv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A2S1UpRw1h4aOYKr8suPBqQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CRXqPXBv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A2S1UpRw1h4aOYKr8suPBqQ.png" alt="Me searching for 3D Models on Yeggi" width="880" height="486"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Me searching for 3D Models on Yeggi&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  II.3) Choosing the micro-controller
&lt;/h4&gt;

&lt;p&gt;The microcontroller est the brain for your keyboard. It converts keystrokes into understandable code used by your PC by implementing the &lt;a href="http://www.simandl.cz/stranky/elektro/keyboard/keyboard_a.htm"&gt;Keyboard Protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Luckily you won’t have to code to much thanks to the community and the Open-source keyboard firmware &lt;a href="https://github.com/qmk/qmk_firmware"&gt;QMK&lt;/a&gt;. With QMK you only need to define the keymap using an &lt;a href="https://config.qmk.fm/#/splitkb/kyria/rev1/LAYOUT"&gt;online configurator&lt;/a&gt;, for instance, then flash the microcontroller.&lt;/p&gt;

&lt;p&gt;The most popular options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://deskthority.net/wiki/Arduino_Pro_Micro"&gt;&lt;strong&gt;Pro Micro&lt;/strong&gt;&lt;/a&gt; most common microcontroller board, you are bringing &lt;a href="https://www.arduino.cc/"&gt;Arduino’s&lt;/a&gt; power to your board.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://deskthority.net/wiki/Elite-C"&gt;&lt;strong&gt;Elite-C&lt;/strong&gt;&lt;/a&gt; higher-end microcontroller, ideal if you plan to add features over time.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nicekeyboards.com/nice-nano/"&gt;&lt;strong&gt;Nice!nano&lt;/strong&gt;&lt;/a&gt; if you want to create a vluthouth keyboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A microcontroller board might be the most expensive element of this build especially given that you need two of them so you could opt-in for onboard keyboards. The controller board I listed essentially relies on &lt;a href="https://www.digikey.com/en/products/detail/microchip-technology/ATMEGA32U4-AUR/2238241"&gt;ATMEGA32U4&lt;/a&gt;. &lt;strong&gt;If you have the skills and knowledge&lt;/strong&gt; you could consider going for &lt;a href="https://www.qualitel.com/what-is-smt-manufacturing/"&gt;SMT assembly&lt;/a&gt; and have your microcontroller solder on the board.&lt;/p&gt;

&lt;h4&gt;
  
  
  II.4) Extra designed feature you may want to consider
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Socket for the microcontroller board&lt;/strong&gt;. Let's say you want to convert the keyboard into a wireless keyboard later socket would allow you to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GgpqtTHo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ABvXsWSWPMxYUzQj_DLBOZw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GgpqtTHo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ABvXsWSWPMxYUzQj_DLBOZw.png" alt="Socket for the microcontroller board" width="880" height="880"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Socket for the microcontroller board&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hot-swappable key switches socket&lt;/strong&gt;. This would allow you to change the key switches at will and prevent you from having to solder the key switches to the board&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dtcZqgzj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AK2DO10HAbM0wVMXcyNE09w.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dtcZqgzj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AK2DO10HAbM0wVMXcyNE09w.jpeg" alt="Kailh Hot-Swap Sockets" width="880" height="432"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Kailh Hot-Swap Sockets courtesy of u/davenelsondotcom&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RGB LEDs.&lt;/strong&gt; Just to make things fancy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ozkaih9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AKhdmZZYc7EVliYo6E6pzJg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ozkaih9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AKhdmZZYc7EVliYo6E6pzJg.png" alt="Corne Split Keyboard from crkbd" width="880" height="585"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Corne Split Keyboard from crkbd&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OLED Arduino module&lt;/strong&gt;. So you can have on the keyboard display&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aHE8PtCl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ARtECCjZTcI8YIDtKFMMeFw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aHE8PtCl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ARtECCjZTcI8YIDtKFMMeFw.jpeg" alt="OLED Arduino module" width="880" height="880"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;OLED Arduino module&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tenting support.&lt;/strong&gt; Tenting consist of adding some inclination to the keyboard so your wrists have a more natural position&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iVmCd9Wt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ABgJhUB-RJRq7Tu3MqOVq0A.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iVmCd9Wt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ABgJhUB-RJRq7Tu3MqOVq0A.jpeg" alt="Split keyboard with Tenting" width="880" height="660"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Split keyboard with Tenting&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I am sure the is a lot more feature we can add to a keyboard 😆.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☑️ Select your features, study the componenents, make adjustement to your PCB.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  III) Designing the case
&lt;/h3&gt;

&lt;p&gt;Last but not least the case, the protective and decorative envelope for your keyboard.&lt;/p&gt;

&lt;p&gt;Some people like the look of the PCB and choose to simply have cut acrylic sheets. The process is simple and requires no machining so it is one of the best options. On top of that if you opted for the LED this would for sure look awesome.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MUngfzwa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AZitXLlO8SAJjaPmV8_90mA%402x.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MUngfzwa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AZitXLlO8SAJjaPmV8_90mA%402x.jpeg" alt="Corne Technician Keyboard Case from littlekeyboards.com" width="880" height="659"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Corne Technician Keyboard Case from littlekeyboards.com&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second option is to have a 3D-printed case. The best would be to start from an existing design and adapt it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☑️ You can screen for existing 3D model with online libraries like : &lt;a href="https://www.yeggi.com/"&gt;https://www.yeggi.com/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Where to get all the parts?
&lt;/h3&gt;

&lt;p&gt;Once you have designed your keyboard you may be wondering: how do I get all the parts?&lt;/p&gt;

&lt;p&gt;With your Gerber file you can get your PCB printed with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/redirect?event=video_description&amp;amp;redir_token=QUFFLUhqa0VqaF9yNFJTTDF0WVQ0ZTlEYnd3Zi1zVGNlQXxBQ3Jtc0tsamZnSFJ5Q3FfSzVhWWp6YXBwWUJsOHIzcGdFR3NuSzEwZXVyVzRuREw2WnBiUVY2bzFNU2xoRW1EbXZFUldOcnJsZDFpOUJ0RDF4VGxISnIwYmlGamdlSVNJeXdGSGxKUXZvQ2Fpd2xmLVZ4LWViZw&amp;amp;q=https%3A%2F%2Fwww.pcbway.com"&gt;https://www.pcbway.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/redirect?event=video_description&amp;amp;redir_token=QUFFLUhqbDVNUlRjZG9xazFxMFdpRWs3ZExhZUhvd25qZ3xBQ3Jtc0tuZjhfZ3ZkaWxZVVBYZFd4QUNhR1BnQkY2UHZwSzgtaThuRXBOMTc5LUpUTFkxRjdna04zV0dWdDNtYWxWY0o4VFVqWWVfREp3T3NBSnlYcFo5QlRFa2ZRZHdFTmJ2WmFLOTVKYzY0RVh5dHZqVjMwNA&amp;amp;q=https%3A%2F%2Fjlcpcb.com"&gt;https://jlcpcb.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the components there is a bunch of dedicated stores online:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/redirect?event=video_description&amp;amp;redir_token=QUFFLUhqa1hFSmx3QmVOSGpOVDE5RG8yWnVhSnM5eVFLZ3xBQ3Jtc0tsdnpGeW53VXc1ZVNVLWtFRlcxLVJ4TlM0YndIa1doX3VPVFhTQTdwVlZkMkV3aTA1a3B1TDBQaXVvZmpwb2g0ODhtV2txcVVrN0ZDS0pwbTc3dVd4S0tUQmstNVJ1RUxHR1lDSU9GODFBdi1PTFhJbw&amp;amp;q=http%3A%2F%2Fmechboards.co.uk"&gt;http://mechboards.co.uk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/redirect?event=video_description&amp;amp;redir_token=QUFFLUhqbTU1MFN6elh0eHlKTmtEZ2VKcWQ0cnlpTHFaUXxBQ3Jtc0trcHJTZjBLemdXNGJGTDQtQVZnSHRzZUludkU5dWp6ZGxoNWd2ekZHaGliLWtXcXliRVFseHRsSGFEbGtiLTVGRjhuZWVfREtVandVdlVaTWdfOVFjYVJ2UzBORzlyTFJ4bGowZEhBX2NwWUdzX3Q2cw&amp;amp;q=http%3A%2F%2Fsplitkb.com"&gt;http://splitkb.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the case and other 3D printed objects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/redirect?event=video_description&amp;amp;redir_token=QUFFLUhqbDVNUlRjZG9xazFxMFdpRWs3ZExhZUhvd25qZ3xBQ3Jtc0tuZjhfZ3ZkaWxZVVBYZFd4QUNhR1BnQkY2UHZwSzgtaThuRXBOMTc5LUpUTFkxRjdna04zV0dWdDNtYWxWY0o4VFVqWWVfREp3T3NBSnlYcFo5QlRFa2ZRZHdFTmJ2WmFLOTVKYzY0RVh5dHZqVjMwNA&amp;amp;q=https%3A%2F%2Fjlcpcb.com"&gt;https://jlcpcb.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://i.materialise.com/"&gt;https://i.materialise.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;❗️️️️ ❗️️️️ Shipping fees seems to be the most expensive part of this build so try to find local manufacturer to reduce the costs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;I am about to order all the parts so I keep this section to share the build 😉.&lt;/p&gt;

&lt;p&gt;Here is the follow-up article with the part I bought (about $40 per keyboard excluding shipping fees):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://couedeloalexandre.medium.com/how-to-build-a-custom-split-mechanical-keyboard-bill-of-material-b2cf1ab62fef"&gt;How to Build a Custom Split Mechanical Keyboard: Bill of material&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;Compare all split keyboard layouts: &lt;a href="https://jhelvy.shinyapps.io/splitkbcompare/"&gt;https://jhelvy.shinyapps.io/splitkbcompare/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More PCB editing tools: &lt;a href="https://support.jlcpcb.com/article/22-how-to-generate-the-gerber-files"&gt;https://support.jlcpcb.com/article/22-how-to-generate-the-gerber-files&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Opensource Keyboard — Corne lines&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MakotoKurauchi/helix"&gt;https://github.com/MakotoKurauchi/helix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/foostan/crkbd"&gt;https://github.com/foostan/crkbd&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opensource Keyboard — GergoPlex Line&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=TpJQtzaThmM"&gt;https://www.youtube.com/watch?v=TpJQtzaThmM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opensource Keyboard — Ferries Line&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pierrechevalier83/ferris"&gt;Original Project: https://github.com/pierrechevalier83/ferris&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/davidphilipbarr/Sweep"&gt;https://github.com/davidphilipbarr/Sweep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=JqpBKuEVinw"&gt;https://www.youtube.com/watch?v=JqpBKuEVinw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/forrestbaer/roost"&gt;https://github.com/forrestbaer/roost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MelindaBirkenstock/Ferris-Slip-On-Case"&gt;https://github.com/MelindaBirkenstock/Ferris-Slip-On-Case&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opensource Keyboard— 3W6 Line&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/weteor/3W6"&gt;https://github.com/weteor/3W6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opensource Keyboard — fifi line&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/raychengy/fifi_split_keeb"&gt;https://github.com/raychengy/fifi_split_keeb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://switchandclick.com/are-ergonomic-keyboards-worth-it/"&gt;https://switchandclick.com/are-ergonomic-keyboards-worth-it/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>startup</category>
      <category>technology</category>
      <category>inspiration</category>
      <category>creativity</category>
    </item>
    <item>
      <title>How to Evaluate the Maturity of DevOps Practices in your Company?</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Wed, 01 Dec 2021 18:58:59 +0000</pubDate>
      <link>https://dev.to/xnok/how-to-evaluate-the-maturity-of-devops-practices-in-your-company-127b</link>
      <guid>https://dev.to/xnok/how-to-evaluate-the-maturity-of-devops-practices-in-your-company-127b</guid>
      <description>&lt;p&gt;&lt;a href="https://faun.pub/how-to-evaluate-the-maturity-of-devops-practices-in-your-company-58a1ff7246a2?source=rss-6e21f0d290d1------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--puLFtUJ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2293/0%2A4y6BdmnidegknODW" alt="" width="880" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Discover SMART Maturity Assessment Inspired by Lean Manufacturing&lt;/p&gt;

&lt;p&gt;&lt;a href="https://faun.pub/how-to-evaluate-the-maturity-of-devops-practices-in-your-company-58a1ff7246a2?source=rss-6e21f0d290d1------2"&gt;Continue reading on FAUN Publication »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>technology</category>
      <category>softwaredevelopment</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Build a “Smarter” DevOps Roadmap - The SMART Maturity Assessment Method</title>
      <dc:creator>Alexandre Couedelo</dc:creator>
      <pubDate>Tue, 23 Nov 2021 11:14:05 +0000</pubDate>
      <link>https://dev.to/xnok/build-a-smarter-devops-roadmap-the-smart-maturity-assessment-method-1gbh</link>
      <guid>https://dev.to/xnok/build-a-smarter-devops-roadmap-the-smart-maturity-assessment-method-1gbh</guid>
      <description>&lt;p&gt;&lt;a href="https://medium.com/geekculture/build-a-smarter-devops-roadmap-the-smart-maturity-assessment-method-bd7f39adfd93?source=rss-6e21f0d290d1------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WWuvwMnR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2600/0%2AbqRDJFCorG-jDrfJ" alt="" width="880" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A tool that helps you build your DevOps Roadmap&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/geekculture/build-a-smarter-devops-roadmap-the-smart-maturity-assessment-method-bd7f39adfd93?source=rss-6e21f0d290d1------2"&gt;Continue reading on Geek Culture »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>technology</category>
      <category>managementandleaders</category>
      <category>softwaredevelopment</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
