<?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: Graham Lyons</title>
    <description>The latest articles on DEV Community by Graham Lyons (@grahamlyons).</description>
    <link>https://dev.to/grahamlyons</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%2F10761%2F6de9a77b-fac9-4a26-abe9-39dedccaf0b5.jpg</url>
      <title>DEV Community: Graham Lyons</title>
      <link>https://dev.to/grahamlyons</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/grahamlyons"/>
    <language>en</language>
    <item>
      <title>A Zero-Fricton Terraform Primer</title>
      <dc:creator>Graham Lyons</dc:creator>
      <pubDate>Mon, 04 Jun 2018 16:30:05 +0000</pubDate>
      <link>https://dev.to/grahamlyons/a-zero-fricton-terraform-primer-58g2</link>
      <guid>https://dev.to/grahamlyons/a-zero-fricton-terraform-primer-58g2</guid>
      <description>&lt;p&gt;What is Terraform and why should you care? How can you learn about it without having to provision real stuff in your Amazon account?&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;Back in the days when we started to get away from dealing with real servers in a rack somewhere a number of cloud infrastructure providers appeared, offering access to their virtual estate via a console and - if they were any good - an API.&lt;/p&gt;

&lt;p&gt;I've worked in lots of different places which used these cloud providers (OK, mainly Amazon Web Services) and I've seen about as many different ways to manage the infrastructure.&lt;/p&gt;

&lt;p&gt;The worst way is of course via the console. Clicking in a GUI is not a good way to make processes repeatable or scalable. Beyond that the options include: CloudFormation, a service from AWS (only works with AWS); HEAT templates from OpenStack, which is very similar to CloudFormation (only works with OpenStack); Chef Provisioning, which is no longer supported by Chef; and of course, Terraform.&lt;/p&gt;

&lt;p&gt;All of these tools allow you to define what infrastructure you'd like - virtual machines, load balancers, block storage, databases etc. - as some kind of machine and human readable language (CloudFormation uses JSON, for example). The tool can interpret the code and create the desired resources; the code can be checked into version control and tracked like application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; is an Infrastructure as Code tool from Hashicorp, who produce other popular pieces of software such as Vagrant. It uses a declarative language, Hashicorp Configuration Language (HCL), to define the desired state of your cloud infrastructure. From this code it generates a dependency graph of the resources and, when run against one or more providers, walks that graph and ensures that the resources exist and are configured as defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;terraform&lt;/code&gt; executable is delivered as a single file so it just needs to be downloaded and put onto your system's path. On a *nix system &lt;code&gt;/usr/local/bin/&lt;/code&gt; is a good place as it's often already on your &lt;code&gt;$PATH&lt;/code&gt; environment variable. From &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;https://www.terraform.io/&lt;/a&gt; find the 'Download' link and select the most appropriate version for your system. Download it, unzip it and put the &lt;code&gt;terraform&lt;/code&gt; file somewhere you can run it, for example &lt;code&gt;/usr/local/bin/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check that it's installed successfully and find out what version you're running - mine is:&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;terraform &lt;span class="nt"&gt;--version&lt;/span&gt;
Terraform v0.11.7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Defining Some Infrastructure
&lt;/h2&gt;

&lt;p&gt;Now that Terraform is installed, we need to define what infrastructure we want it to create. We use HCL to define resources for different providers. A simple one is the &lt;a href="https://www.terraform.io/docs/providers/random/index.html" rel="noopener noreferrer"&gt;random provider&lt;/a&gt;, which generates random data to use, for example, as server names. It doesn't operate against a cloud provider and requires no API keys etc. so is good to illustrate Terraform's workflow. We'll also use the &lt;a href="https://www.terraform.io/docs/providers/local/index.html" rel="noopener noreferrer"&gt;local provider&lt;/a&gt; to write that random data out to a file.&lt;/p&gt;

&lt;p&gt;Put the following into a file called &lt;code&gt;example.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"name_length"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The number of words to put into the random name"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_pet"&lt;/span&gt; &lt;span class="s2"&gt;"server"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.name_length}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"local_file"&lt;/span&gt; &lt;span class="s2"&gt;"random"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                   
  &lt;span class="nx"&gt;content&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${random_pet.server.id}"&lt;/span&gt;                                        
  &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/random.txt"&lt;/span&gt;                                         
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${random_pet.server.id}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;em&gt;Aside: Code Organisation&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Terraform will look in the directory you tell it to (the current directory by default) and find all of the &lt;code&gt;*.tf&lt;/code&gt; files - sub-directories are ignored. It'll treat the files it finds as one single definition and draw a graph of all the resources. It's common to see the &lt;code&gt;variable&lt;/code&gt;s and &lt;code&gt;output&lt;/code&gt;s split into different files to make it clear where to find them. It's also common, and a good idea, to split code into modules but we won't worry about that today.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the same directory run: &lt;code&gt;terraform init&lt;/code&gt;. You should see output which looks a bit like this:&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;terraform init

Initializing provider plugins...
- Checking &lt;span class="k"&gt;for &lt;/span&gt;available provider plugins on https://releases.hashicorp.com...
- Downloading plugin &lt;span class="k"&gt;for &lt;/span&gt;provider &lt;span class="s2"&gt;"random"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;1.3.1&lt;span class="o"&gt;)&lt;/span&gt;...
- Downloading plugin &lt;span class="k"&gt;for &lt;/span&gt;provider &lt;span class="s2"&gt;"local"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;1.1.0&lt;span class="o"&gt;)&lt;/span&gt;...

The following providers &lt;span class="k"&gt;do &lt;/span&gt;not have any version constraints &lt;span class="k"&gt;in &lt;/span&gt;configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; constraints to the
corresponding provider blocks &lt;span class="k"&gt;in &lt;/span&gt;configuration, with the constraint strings
suggested below.

&lt;span class="k"&gt;*&lt;/span&gt; provider.local: version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.1"&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; provider.random: version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.3"&lt;/span&gt;

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running &lt;span class="s2"&gt;"terraform plan"&lt;/span&gt; to see
any changes that are required &lt;span class="k"&gt;for &lt;/span&gt;your infrastructure. All Terraform commands
should now work.

If you ever &lt;span class="nb"&gt;set &lt;/span&gt;or change modules or backend configuration &lt;span class="k"&gt;for &lt;/span&gt;Terraform,
rerun this &lt;span class="nb"&gt;command &lt;/span&gt;to reinitialize your working directory. If you forget, other
commands will detect it and remind you to &lt;span class="k"&gt;do &lt;/span&gt;so &lt;span class="k"&gt;if &lt;/span&gt;necessary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform has looked at all of the &lt;code&gt;*.tf&lt;/code&gt; files, determined which providers are being used and has downloaded the appropriate plugins. These are stored in the &lt;code&gt;.terraform/&lt;/code&gt; directory which has been created in the current path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning changes
&lt;/h2&gt;

&lt;p&gt;One amazing feature of Terraform is the ability to preview changes to get an idea of what's actually going to happen when you apply them. Is this change going to modify my loadbalancer in-place or is it going to destroy it and recreate it, taking my application offline for precious minutes?&lt;/p&gt;

&lt;p&gt;Let's see what that looks like:&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;terraform plan
Refreshing Terraform state &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-memory&lt;/span&gt; prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to &lt;span class="nb"&gt;local &lt;/span&gt;or remote state storage.


&lt;span class="nt"&gt;------------------------------------------------------------------------&lt;/span&gt;

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + local_file.random
      &lt;span class="nb"&gt;id&lt;/span&gt;:        &amp;lt;computed&amp;gt;
      content:   &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;random_pet&lt;/span&gt;&lt;span class="p"&gt;.server.id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      filename:  &lt;span class="s2"&gt;"/home/vagrant/workspace/tfdemo/random.txt"&lt;/span&gt;

  + random_pet.server
      &lt;span class="nb"&gt;id&lt;/span&gt;:        &amp;lt;computed&amp;gt;
      length:    &lt;span class="s2"&gt;"2"&lt;/span&gt;
      separator: &lt;span class="s2"&gt;"-"&lt;/span&gt;


Plan: 2 to add, 0 to change, 0 to destroy.

&lt;span class="nt"&gt;------------------------------------------------------------------------&lt;/span&gt;

Note: You didn&lt;span class="s1"&gt;'t specify an "-out" parameter to save this plan, so Terraform
can'&lt;/span&gt;t guarantee that exactly these actions will be performed &lt;span class="k"&gt;if&lt;/span&gt;
&lt;span class="s2"&gt;"terraform apply"&lt;/span&gt; is subsequently run.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the first time we're running it so all the resources we've specified are being created - see the &lt;code&gt;+&lt;/code&gt; next to their name in the output.&lt;/p&gt;

&lt;p&gt;Also pay attention to the "Note" - we haven't saved this plan so whilst we've got a good idea what Terraform will do when we apply it, it's not guaranteed to try to do the same thing. We can store the plan in a file with a unique name by appending a timestamp i.e. &lt;code&gt;terraform plan -out "plan-$(date +%s)"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Running that we instead get this at the end of the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;...
This plan was saved to: plan-1527707537

To perform exactly these actions, run the following &lt;span class="nb"&gt;command &lt;/span&gt;to apply:
    terraform apply &lt;span class="s2"&gt;"plan-1527707537"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we're happy with this plan then we can apply it for real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying Changes
&lt;/h2&gt;

&lt;p&gt;When we run &lt;code&gt;terraform apply&lt;/code&gt; and pass it the plan file we get output which looks like the following:&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;terraform apply &lt;span class="s2"&gt;"plan-1527707537"&lt;/span&gt;
random_pet.server: Creating...
  length:    &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"2"&lt;/span&gt;
  separator: &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;
random_pet.server: Creation &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s &lt;span class="o"&gt;(&lt;/span&gt;ID: leading-piranha&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Creating...
  content:  &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"leading-piranha"&lt;/span&gt;
  filename: &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/home/vagrant/workspace/tfdemo/random.txt"&lt;/span&gt;
local_file.random: Creation &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s &lt;span class="o"&gt;(&lt;/span&gt;ID: 681f312327eab60da028b397bc85af8682fdc185&lt;span class="o"&gt;)&lt;/span&gt;

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

name &lt;span class="o"&gt;=&lt;/span&gt; leading-piranha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Random provider gave us a pet name consisting of 2 words and the &lt;code&gt;output&lt;/code&gt; directive showed it at the end of the program. The &lt;code&gt;local_file&lt;/code&gt; resource wrote the name into a file called &lt;code&gt;random.txt&lt;/code&gt; in the current directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat random.txt
leading-piranha[vagrant@localhost tfdemo]$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making More Changes
&lt;/h2&gt;

&lt;p&gt;Hmmm, there's no newline at the end of the file. I'd prefer it to be formatted with one so I'll add one into the HCL. The &lt;code&gt;content&lt;/code&gt; in the &lt;code&gt;local_file&lt;/code&gt; can be changed to, with a &lt;code&gt;\n&lt;/code&gt; appended:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;     &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${random_pet.server.id}&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;n"&lt;/span&gt;                                      
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we plan the changes we'll see that &lt;em&gt;only&lt;/em&gt; the file is scheduled to change. There's no reason for the &lt;code&gt;random_pet&lt;/code&gt; resource to be changed at all so Terraform uses it as it is.&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;terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt; &lt;span class="s2"&gt;"plan-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

Refreshing Terraform state &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-memory&lt;/span&gt; prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to &lt;span class="nb"&gt;local &lt;/span&gt;or remote state storage.

random_pet.server: Refreshing state... &lt;span class="o"&gt;(&lt;/span&gt;ID: leading-piranha&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Refreshing state... &lt;span class="o"&gt;(&lt;/span&gt;ID: 681f312327eab60da028b397bc85af8682fdc185&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nt"&gt;------------------------------------------------------------------------&lt;/span&gt;

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and &lt;span class="k"&gt;then &lt;/span&gt;create replacement

Terraform will perform the following actions:

-/+ local_file.random &lt;span class="o"&gt;(&lt;/span&gt;new resource required&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;id&lt;/span&gt;:       &lt;span class="s2"&gt;"681f312327eab60da028b397bc85af8682fdc185"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &amp;lt;computed&amp;gt; &lt;span class="o"&gt;(&lt;/span&gt;forces new resource&lt;span class="o"&gt;)&lt;/span&gt;
      content:  &lt;span class="s2"&gt;"leading-piranha"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"leading-piranha&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;forces new resource&lt;span class="o"&gt;)&lt;/span&gt;
      filename: &lt;span class="s2"&gt;"/home/vagrant/workspace/tfdemo/random.txt"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/home/vagrant/workspace/tfdemo/random.txt"&lt;/span&gt;


Plan: 1 to add, 0 to change, 1 to destroy.

&lt;span class="nt"&gt;------------------------------------------------------------------------&lt;/span&gt;

This plan was saved to: plan-1528126805

To perform exactly these actions, run the following &lt;span class="nb"&gt;command &lt;/span&gt;to apply:
    terraform apply &lt;span class="s2"&gt;"plan-1528126805"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applying the changes from the plan we've just made can &lt;code&gt;cat&lt;/code&gt;ing the file again shows that there's now a newline at the end:&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;terraform apply &lt;span class="s2"&gt;"plan-1528126805"&lt;/span&gt;
local_file.random: Destroying... &lt;span class="o"&gt;(&lt;/span&gt;ID: 681f312327eab60da028b397bc85af8682fdc185&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Destruction &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s
local_file.random: Creating...
  content:  &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"leading-piranha&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  filename: &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/home/vagrant/workspace/tfdemo/random.txt"&lt;/span&gt;
local_file.random: Creation &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s &lt;span class="o"&gt;(&lt;/span&gt;ID: 82c2862c8ae7053eb94b7aa498265335c5d22b22&lt;span class="o"&gt;)&lt;/span&gt;

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 1 added, 0 changed, 1 destroyed.

Outputs:

name &lt;span class="o"&gt;=&lt;/span&gt; leading-piranha

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;random.txt
leading-piranha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
   Variables
&lt;/h2&gt;

&lt;p&gt;In the &lt;code&gt;example.tf&lt;/code&gt; file you can see that we declared a &lt;code&gt;variable&lt;/code&gt; called &lt;code&gt;name_length&lt;/code&gt; and referenced it in the &lt;code&gt;random_pet&lt;/code&gt; resource (&lt;code&gt;length = "${var.name_length}"&lt;/code&gt;); why not just hard code that number?&lt;/p&gt;

&lt;p&gt;To aid code reuse, Terraform lets us pass in different values for the variables we've defined. We use the &lt;code&gt;-var&lt;/code&gt; flag and the name of the variable, like this:&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;terraform plan &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;name_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
Refreshing Terraform state &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-memory&lt;/span&gt; prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to &lt;span class="nb"&gt;local &lt;/span&gt;or remote state storage.

random_pet.server: Refreshing state... &lt;span class="o"&gt;(&lt;/span&gt;ID: leading-piranha&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Refreshing state... &lt;span class="o"&gt;(&lt;/span&gt;ID: 82c2862c8ae7053eb94b7aa498265335c5d22b22&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nt"&gt;------------------------------------------------------------------------&lt;/span&gt;

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and &lt;span class="k"&gt;then &lt;/span&gt;create replacement

Terraform will perform the following actions:

-/+ local_file.random &lt;span class="o"&gt;(&lt;/span&gt;new resource required&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;id&lt;/span&gt;:        &lt;span class="s2"&gt;"82c2862c8ae7053eb94b7aa498265335c5d22b22"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &amp;lt;computed&amp;gt; &lt;span class="o"&gt;(&lt;/span&gt;forces new resource&lt;span class="o"&gt;)&lt;/span&gt;
      content:   &lt;span class="s2"&gt;"leading-piranha&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;random_pet&lt;/span&gt;&lt;span class="p"&gt;.server.id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;forces new resource&lt;span class="o"&gt;)&lt;/span&gt;
      filename:  &lt;span class="s2"&gt;"/home/vagrant/workspace/tfdemo/random.txt"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/home/vagrant/workspace/tfdemo/random.txt"&lt;/span&gt;

-/+ random_pet.server &lt;span class="o"&gt;(&lt;/span&gt;new resource required&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;id&lt;/span&gt;:        &lt;span class="s2"&gt;"leading-piranha"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &amp;lt;computed&amp;gt; &lt;span class="o"&gt;(&lt;/span&gt;forces new resource&lt;span class="o"&gt;)&lt;/span&gt;
      length:    &lt;span class="s2"&gt;"2"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;forces new resource&lt;span class="o"&gt;)&lt;/span&gt;
      separator: &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;


Plan: 2 to add, 0 to change, 2 to destroy.

&lt;span class="nt"&gt;------------------------------------------------------------------------&lt;/span&gt;

Note: You didn&lt;span class="s1"&gt;'t specify an "-out" parameter to save this plan, so Terraform
can'&lt;/span&gt;t guarantee that exactly these actions will be performed &lt;span class="k"&gt;if&lt;/span&gt;
&lt;span class="s2"&gt;"terraform apply"&lt;/span&gt; is subsequently run.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plan tells us again what's going to happen - both resources will be destroyed and others created in their place. The file has to be recreated in this case because it's dependent on the value from &lt;code&gt;random_pet&lt;/code&gt;. Terraform works this out from the dependency graph it generates - it can work out what it needs to recreate based on what's changed and what depends on that.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Aside: Dependency Graph&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;The dependency graph for your infrastructure can be seen in the&lt;/em&gt; [DOT language](&lt;a href="https://en.wikipedia.org/wiki/DOT_(graph_description_language)" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/DOT_(graph_description_language)&lt;/a&gt; &lt;em&gt;by running &lt;code&gt;terraform graph&lt;/code&gt;. If you've got&lt;/em&gt; &lt;a href="http://www.graphviz.org/" rel="noopener noreferrer"&gt;Graphviz&lt;/a&gt; &lt;em&gt;installed then you can render it by piping the output straight to the &lt;code&gt;dot&lt;/code&gt; program:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform graph | dot &lt;span class="nt"&gt;-Tpng&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; tfdemo.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a really simple example and no critical infrastructure is at stake so we can apply these changes without saving to a plan file by simply running &lt;code&gt;terraform apply&lt;/code&gt; and either typing "yes" at the prompt or passing the &lt;code&gt;-auto-approve&lt;/code&gt; flag:&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;terraform apply &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;name_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
random_pet.server: Refreshing state... &lt;span class="o"&gt;(&lt;/span&gt;ID: leading-piranha&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Refreshing state... &lt;span class="o"&gt;(&lt;/span&gt;ID: 82c2862c8ae7053eb94b7aa498265335c5d22b22&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Destroying... &lt;span class="o"&gt;(&lt;/span&gt;ID: 82c2862c8ae7053eb94b7aa498265335c5d22b22&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Destruction &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s
random_pet.server: Destroying... &lt;span class="o"&gt;(&lt;/span&gt;ID: leading-piranha&lt;span class="o"&gt;)&lt;/span&gt;
random_pet.server: Destruction &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s
random_pet.server: Creating...
  length:    &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
  separator: &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;
random_pet.server: Creation &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s &lt;span class="o"&gt;(&lt;/span&gt;ID: scarcely-intense-mammoth&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Creating...
  content:  &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"scarcely-intense-mammoth&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  filename: &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/home/vagrant/workspace/tfdemo/random.txt"&lt;/span&gt;
local_file.random: Creation &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s &lt;span class="o"&gt;(&lt;/span&gt;ID: a3f2f24388d1e4ddd72872a833469002f2ad5b75&lt;span class="o"&gt;)&lt;/span&gt;

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 2 added, 0 changed, 2 destroyed.

Outputs:

name &lt;span class="o"&gt;=&lt;/span&gt; scarcely-intense-mammoth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we need to pass the same parameters to the apply phase that we passed in planning. This is one very good reason to save the plan and use that when running &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  State
&lt;/h2&gt;

&lt;p&gt;Along with the &lt;code&gt;.terraform/&lt;/code&gt; directory which stores the provider plugins you'll notice that there's a &lt;code&gt;terraform.tfstate&lt;/code&gt; file there too. A quick examination shows that it's text, which we can read!&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;file terraform.tfstate
terraform.tfstate: ASCII text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the state of our resources, in JSON format. The state represents Terraform's view of the defined resources. If you run the &lt;code&gt;plan&lt;/code&gt; command with the same arguments in the same directory then Terraform will tell us that there's nothing to do:&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;terraform plan &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;name_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3

Refreshing Terraform state &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-memory&lt;/span&gt; prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to &lt;span class="nb"&gt;local &lt;/span&gt;or remote state storage.

random_pet.server: Refreshing state... &lt;span class="o"&gt;(&lt;/span&gt;ID: scarcely-intense-mammoth&lt;span class="o"&gt;)&lt;/span&gt;
local_file.random: Refreshing state... &lt;span class="o"&gt;(&lt;/span&gt;ID: a3f2f24388d1e4ddd72872a833469002f2ad5b75&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nt"&gt;------------------------------------------------------------------------&lt;/span&gt;

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're running Terraform to create your cloud infrastructure then make sure the state is committed to source control or - particularly if you're working with other engineers - persisted in one of the &lt;a href="https://www.terraform.io/docs/backends/index.html" rel="noopener noreferrer"&gt;supported backends&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This illustrates a typical workflow for Terraform: code -&amp;gt; plan -&amp;gt; apply -&amp;gt; commit. To make it as easy as possible to follow along we've used providers which only operate locally, but if you added an &lt;code&gt;aws_instance&lt;/code&gt; resource then the random server name we've generated could easily be used to set the &lt;code&gt;Name&lt;/code&gt; tag on the EC2 instance. Terraform will pick up the standard &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; environment variables and your workflow remains unchanged as you provision real infrastructure.&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>devops</category>
      <category>terraform</category>
      <category>hashicorp</category>
    </item>
    <item>
      <title>Why My Development Environment is the Best</title>
      <dc:creator>Graham Lyons</dc:creator>
      <pubDate>Wed, 11 Apr 2018 16:04:28 +0000</pubDate>
      <link>https://dev.to/grahamlyons/why-my-development-environment-is-the-best-30ga</link>
      <guid>https://dev.to/grahamlyons/why-my-development-environment-is-the-best-30ga</guid>
      <description>&lt;h1&gt;
  
  
  Why My Development Environment is the Best
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Or more accurately, what works for me right now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As software developers we spend a lot of our day at the keyboard, typing. It's natural that we spend time making that environment a pleasant and productive place in which to work.&lt;/p&gt;

&lt;p&gt;Local development environments are often very personal and are tweaked and customised to the tastes of the individual developer. There are a finite number of editors and IDEs but an almost infinite combination of plugins, themes and customisations within those. In some of the more recent evolutions of my local environment I've rebelled against the culture of personalisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Tools
&lt;/h2&gt;

&lt;p&gt;Almost all the software I write gets deployed and run in production on a server running some flavour of Linux. I run everything locally on a &lt;a href="https://en.wikipedia.org/wiki/Virtual_machine" rel="noopener noreferrer"&gt;virtual machine&lt;/a&gt; which is set up as close to production as I can get. Over the years this has been invaluable for catching bugs before they even get to a staging environment or reproducing production problems under safe conditions.&lt;/p&gt;

&lt;p&gt;To manage and run virtual machines (VMs) I use the combination of &lt;a href="https://www.virtualbox.org/" rel="noopener noreferrer"&gt;VirtualBox&lt;/a&gt; and &lt;a href="https://www.vagrantup.com/" rel="noopener noreferrer"&gt;Vagrant&lt;/a&gt;. Once I've installed those I'm (nearly) ready to run a VM. I also use the &lt;a href="https://github.com/dotless-de/vagrant-vbguest" rel="noopener noreferrer"&gt;vagrant-vbguest plugin&lt;/a&gt; which manages the installation of the VirtualBox Guest Additions in the VM itself. These are used to share a workspace directory between my host machine and the Linux VM: &lt;code&gt;vagrant plugin install vagrant-vbguest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The configuration for a VM managed by Vagrant can be stored as code so here's an example on GitHub of the main environment I use at the moment: &lt;a href="https://github.com/grahamlyons/centos-dev" rel="noopener noreferrer"&gt;https://github.com/grahamlyons/centos-dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To run it: clone the repo, start the VM and then connect to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:grahamlyons/centos-dev.git
&lt;span class="nb"&gt;cd &lt;/span&gt;centos-dev
vagrant up &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; vagrant ssh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'tmux attach || tmux'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instructions in the &lt;code&gt;Vagrantfile&lt;/code&gt; start from a CentOS 7 base image and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;specify a fixed IP address which can be used to refer to the VM&lt;/li&gt;
&lt;li&gt;mount a local &lt;code&gt;~/workspace/&lt;/code&gt; directory inside the VM (at the same path)&lt;/li&gt;
&lt;li&gt;install some base packages, e.g. &lt;code&gt;tmux&lt;/code&gt;, &lt;code&gt;vim&lt;/code&gt;, &lt;code&gt;git&lt;/code&gt; etc.&lt;/li&gt;
&lt;li&gt;install and sets up Docker&lt;/li&gt;
&lt;li&gt;copy some local configuration and credential files into the VM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SSH connection command also puts me into either an existing &lt;a href="https://en.wikipedia.org/wiki/Tmux" rel="noopener noreferrer"&gt;tmux&lt;/a&gt; session or starts a new one. Tmux is a great tool for creating multiple tabs and panes in a single SSH session. I don't use any extra configuration for it beyond what's installed on CentOS with the package.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of a Virtual machine
&lt;/h3&gt;

&lt;p&gt;The first time I was introduced to working inside a VM I was sold on it completely. It made so much sense to me to be as close to production as possible and installing software on Linux, using a proper package manager, is so much nicer than on OSX (or Windows - in the depths of my memory).&lt;/p&gt;

&lt;p&gt;With the shared folder - &lt;code&gt;~/workspace/&lt;/code&gt; in my case - I can use whatever editor I like on my host OS and the changes will always be inside the VM, ready to run.&lt;/p&gt;

&lt;p&gt;Running a VM has also saved me from completely destroying my machine on more than one occasion, the worst of which was an accidental &lt;code&gt;rm -rf /&lt;/code&gt; run as &lt;code&gt;root&lt;/code&gt;. Always having a working machine that you can use to search for help with fixing problems is incredibly useful. If things get really back you can just destroy it and start again from your known good state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks of a Virtual machine
&lt;/h3&gt;

&lt;p&gt;Using a VM is not a perfect solution and running code inside a directory shared between the host machine and the guest VM can give performance problems. The shared directory is great for use with a simple editor but with an IDE, which will want to run your code for you, it can be complicated or impossible to run the code inside the virtual machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Editor
&lt;/h2&gt;

&lt;p&gt;The first editor I ever used when I started working professionally was &lt;a href="https://en.wikipedia.org/wiki/Macromedia_HomeSite" rel="noopener noreferrer"&gt;Homesite&lt;/a&gt;, which betrays my vintage. After I was no longer able to get hold of that I looked around for something else and saw something called &lt;a href="https://www.vim.org/" rel="noopener noreferrer"&gt;Vim&lt;/a&gt; recommended. I was interested and downloaded it and opened it up. After a few minutes I managed to work out how to quit it and didn't open it up again for a year or two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vim
&lt;/h3&gt;

&lt;p&gt;After throwing myself into Vim I now use it almost exclusively. Where I use something else I try to find a Vim key-bindings plugin for it. There is a big learning curve for Vim, and &lt;code&gt;vimtutor&lt;/code&gt; was a big help, but now that I'm familiar with the movements and actions nothing lets me manipulate text faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugins and Configuration
&lt;/h3&gt;

&lt;p&gt;My &lt;code&gt;.vimrc&lt;/code&gt; file has gone through many iterations and it's now roughly 10 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;expandtab&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;shiftwidth&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;tabstop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;

&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;modeline&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;modelines&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="k"&gt;nu&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;colorcolumn&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;

&lt;span class="nb"&gt;syntax&lt;/span&gt; enable

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;g:netrw_liststyle&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"*netrw_gitignore#Hide"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;g:netrw_list_hide&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;netrw_gitignore#Hide&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use spaces instead of tabs (who wouldn't?); (for OSX, where it was turned off) it's set to read Vim settings from the tops of files (&lt;a href="http://vim.wikia.com/wiki/Modeline_magic" rel="noopener noreferrer"&gt;http://vim.wikia.com/wiki/Modeline_magic&lt;/a&gt;); line numbers and syntax highlighting are on; there's a column at 80 characters to stop my lines from getting too long and I've set directory listings to look like a tree.&lt;/p&gt;

&lt;p&gt;Everything else I use in Vim is vanilla. Just getting used to the defaults allows me to move between different machines more easily and there's less of my clever customisation and tweaking to remember and more widely available documentation to refer to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Software
&lt;/h2&gt;

&lt;p&gt;Over the past couple of years I've started running almost everything inside Docker containers so I'm &lt;code&gt;yum install&lt;/code&gt;ing less and less. Almost everything is available in an image from Docker Hub and it's so fast to start up once it's been pulled down that it just makes so much sense. Running different versions of e.g. Node, Ruby or Python side by side is much simpler.&lt;/p&gt;

&lt;p&gt;I still use &lt;code&gt;yum&lt;/code&gt; to install utility packages like &lt;code&gt;telnet&lt;/code&gt; or &lt;code&gt;jq&lt;/code&gt;, and they'll often make it into the configuration in the &lt;code&gt;Vagrantfile&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Works for Me for Now
&lt;/h2&gt;

&lt;p&gt;So this is how my machine is set up at the moment, and it works really well for me. I run OSX and spend most of the time in Terminal, with one tab for my VM connection. I use Vim both on OSX and on the VM, and the same for Git.&lt;/p&gt;

&lt;p&gt;It works well but I am always making changes. The introduction of Docker is more recent and is becoming more prominent. Let's see what this looks like in a year.&lt;/p&gt;

</description>
      <category>devtips</category>
      <category>productivity</category>
      <category>infrastructure</category>
      <category>development</category>
    </item>
    <item>
      <title>Machine Learning for the Lazy Beginner</title>
      <dc:creator>Graham Lyons</dc:creator>
      <pubDate>Mon, 12 Feb 2018 13:33:18 +0000</pubDate>
      <link>https://dev.to/grahamlyons/machine-learning-for-the-lazy-beginner--3c1</link>
      <guid>https://dev.to/grahamlyons/machine-learning-for-the-lazy-beginner--3c1</guid>
      <description>&lt;h1&gt;
  
  
  Machine Learning for the Lazy Beginner
&lt;/h1&gt;

&lt;p&gt;This article was prompted by a tweet I saw which asked for a walkthrough on training a machine learning service to recognise new members of 3 different data sets.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/rem"&gt;@rem&lt;/a&gt;: Being lazy here: I'm after a (machine learning) service that I can feed three separate datasets (to train with), and then I want to ask: "which dataset is &lt;em&gt;this&lt;/em&gt; new bit of content most like".&lt;/p&gt;

&lt;p&gt;Is there a walkthrough/cheatsheet/service for this?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My first thought was that this sounds like a &lt;a href="https://en.wikipedia.org/wiki/Statistical_classification" rel="noopener noreferrer"&gt;&lt;em&gt;classification&lt;/em&gt;&lt;/a&gt; task, and the idea that there are 3 sets of data should be the other way round: there is one set of data and each item in the set has one of 3 labels.&lt;/p&gt;

&lt;p&gt;I didn't have a walkthrough in mind but I do know how to train a classifier to perform this exact task, so here is my walkthrough of classifying text documents using Javascript.&lt;/p&gt;

&lt;h2&gt;
  
  
   Do You Have Adequate Supervision?
&lt;/h2&gt;

&lt;p&gt;Machine learning can be classified (no pun intended) as either supervised or unsupervised. The latter refers to problems where the data you feed to the algorithm has no predetermined label. You might have a bunch of text documents and you want to find out if they can be grouped together into similar categories - that would be an example of &lt;a href="https://en.wikipedia.org/wiki/Cluster_analysis" rel="noopener noreferrer"&gt;&lt;em&gt;clustering&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Supervised learning is where you know the outcome already. You have set of data in which each member fits into one of &lt;em&gt;n&lt;/em&gt; categories, for example a set of data on customers to your e-commerce platform, labelled according to what category of product they're likely to be interested in. You train your model against that data and use it predict what new customers might be interested in buying - this is an example of classification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get in Training
&lt;/h2&gt;

&lt;p&gt;For the classification task we've said that we "train" a model against the data we know the labels for. What that means is that we feed each instance in a dataset into the classifier, saying which label it should have. We can then pass the classifier a new instance, to which we don't know the label, and it will predict which class that fits into, based on what it's seen before.&lt;/p&gt;

&lt;p&gt;There's a Javascript package called &lt;a href="https://www.npmjs.com/package/natural" rel="noopener noreferrer"&gt;&lt;code&gt;natural&lt;/code&gt;&lt;/a&gt; which has several different classifiers for working with text documents (natural language). Using one looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BayesClassifier&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;natural&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BayesClassifier&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Feed documents in, labelled either 'nice' or 'nasty'&lt;/span&gt;
&lt;span class="nx"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are lovely&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I really like you&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are horrible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nasty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I do not like you&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nasty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Train the model&lt;/span&gt;
&lt;span class="nx"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Predict which label these documents should have&lt;/span&gt;
&lt;span class="nx"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You smell horrible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// nasty&lt;/span&gt;
&lt;span class="nx"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I like your face&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 'nice'&lt;/span&gt;
&lt;span class="nx"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are nice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 'nice'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add labelled data, train the model and then we can use it to predict the class of text we haven't seen before. Hooray!&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Analysis
&lt;/h2&gt;

&lt;p&gt;Training a machine learning model with a dataset of 4 instances clearly isn't something that's going to be very useful - its experience of the problem domain is very limited. Machine learning and big data are somewhat synonymous because the more data you have the better you can train your model, in the same way that the more experience someone has of a topic the more they're likely to know about it. So how do we know how clever our model is?&lt;/p&gt;

&lt;p&gt;The way we evaluate supervised learning models is to split our data into a training set and a testing set, train it using one and test it using the other (I'll leave you to guess which way round). The more data in the training set the better.&lt;/p&gt;

&lt;p&gt;When we get the predictions for our test data we can determine if the model accurately predicted the class each item is labelled with. Adding up the successes and errors will give us numbers indicating how good the classifier is. For example, successes over total instances processed is our accuracy; errors divided by the total is the error rate. We can get more in-depth analysis by plotting a &lt;a href="https://en.wikipedia.org/wiki/Confusion_matrix" rel="noopener noreferrer"&gt;&lt;em&gt;confusion matrix&lt;/em&gt;&lt;/a&gt; showing actual classes against predictions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Actual&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;nice&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;nasty&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Predicted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;nice&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;nasty&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is really valuable for assessing performance when it's OK to incorrectly predict one class but not another. For example, when screening for terminal diseases it would be much better to bias for false positives and have a doctor check images manually rather than incorrectly give some patients the all clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Train On All the Data
&lt;/h2&gt;

&lt;p&gt;One way to train with as much data as possible is to use &lt;a href="https://en.wikipedia.org/wiki/Cross-validation_%28statistics%29" rel="noopener noreferrer"&gt;&lt;em&gt;cross validation&lt;/em&gt;&lt;/a&gt;, where we take a small subset of our data to test on and use the rest for training. A commonly used technique is &lt;em&gt;k-fold&lt;/em&gt; cross validation, where the dataset is divided into &lt;em&gt;k&lt;/em&gt; different subsets (&lt;em&gt;k&lt;/em&gt; can be any number, even the number of instances in the dataset), each of which is used as a testing set while the rest is used for training - the process is repeated until each subset has been used for testing i.e. &lt;em&gt;k&lt;/em&gt; times.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyv86dd7wkflqdjg5jswr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyv86dd7wkflqdjg5jswr.jpg" alt="k-fold cross validation" width="526" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tweet Data Example
&lt;/h2&gt;

&lt;p&gt;I've put together an example using the &lt;code&gt;natural&lt;/code&gt; Javascript package. It gets data from Twitter, searching for 3 different hashtags, then trains a model using those 3 hashtags as classes and evaluates the performance of the trained model. The output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node gather.js
Found 93 for #javascript
Found 100 for #clojure
Found 68 for #python

$ node train.js
{ positives: 251, negatives: 10 }
Accuracy: 96.17%
Error: 3.83%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is on Github: &lt;a href="https://github.com/grahamlyons/classification-js" rel="noopener noreferrer"&gt;classification-js&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Machine Learning is That Easy?!
&lt;/h2&gt;

&lt;p&gt;Well, no. The example is really trivial and doesn't do any pre-processing on the gathered data: it doesn't strip out the hashtag that it searched for from the text (meaning that it would probably struggle to predict a tweet about Python that didn't include "#python"); it doesn't remove any &lt;a href="https://en.wikipedia.org/wiki/Stop_words" rel="noopener noreferrer"&gt;&lt;em&gt;stop words&lt;/em&gt;&lt;/a&gt; (words that don't really add any value, such as &lt;em&gt;a&lt;/em&gt; or &lt;em&gt;the&lt;/em&gt;. In fact, &lt;code&gt;natural&lt;/code&gt; does this for us when we feed documents in, but we didn't know that...); it doesn't expand any of the shortened URLs in the text (&lt;em&gt;learnjavascript.com&lt;/em&gt; surely means more than &lt;em&gt;t.co&lt;/em&gt;). We don't even look at the gathered data before using it, for example graphing word-frequencies to get an idea of what we've got: are some of the "#python" tweets from snake enthusiasts talking about their terrariums?&lt;/p&gt;

&lt;p&gt;To miss-quote Tom Lehrer, machine learning is like a sewer: what you get out depends on what you put in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The aim of this article was to give an overview of how a machine learning model is trained to perform a classification task. Hopefully, for the beginner, this goes some way to lifting the lid on some of that mystery.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by: &lt;a href="https://www.flickr.com/photos/mattbuck007/" rel="noopener noreferrer"&gt;https://www.flickr.com/photos/mattbuck007/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Everything You Need To Know About Networking On AWS</title>
      <dc:creator>Graham Lyons</dc:creator>
      <pubDate>Sun, 28 Jan 2018 19:40:07 +0000</pubDate>
      <link>https://dev.to/grahamlyons/everything-you-need-to-know-about-networking-on-aws-4bkf</link>
      <guid>https://dev.to/grahamlyons/everything-you-need-to-know-about-networking-on-aws-4bkf</guid>
      <description>&lt;h1&gt;
  
  
  Everything You Need To Know About Networking On AWS
&lt;/h1&gt;

&lt;p&gt;Disclaimer: I'm not a network engineer and never have been - a tame network engineer has been consulted to ensure factual and terminological accuracy. The following is an in-exhaustive run down of everything I've learnt from building and using network infrastructure on Amazon Web Services. If you find you have no reference point for this information then have a poke around the "VPC" section of the AWS control panel (or get in touch to tell me I'm talking nonsense).&lt;/p&gt;

&lt;h2&gt;
  
  
  Parts of a Network You Should Know About
&lt;/h2&gt;

&lt;p&gt;If you're running infrastructure and applications on AWS then you will encounter all of these things. They're not the only parts of a network setup but they are, in my experience, the most important ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  VPC
&lt;/h3&gt;

&lt;p&gt;A virtual private cloud - VPC - is a private network space in which you can run your infrastructure. It has an address space (CIDR range) which you choose e.g. &lt;code&gt;10.0.0.0/16&lt;/code&gt;. This determines how many IP addresses you can assign within the VPC. Each server you create inside the VPC will need an IP address so this address space defines the limit of how many resources you can have within the network. The &lt;code&gt;10.0.0.0/16&lt;/code&gt; address space can use the addresses from &lt;code&gt;10.0.0.0&lt;/code&gt; to &lt;code&gt;10.0.255.255&lt;/code&gt;, which is 65,536 IP addresses.&lt;/p&gt;

&lt;p&gt;The VPC is the basis of your network on AWS and all new accounts include a default VPC with a subnets in each availability zone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------+
|     VPC       |     The Internet
|               |
|               |
|  10.0.0.0/16  |
|               |
|               |
+---------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Subnets
&lt;/h3&gt;

&lt;p&gt;A subnet is a section of your VPC, with its own CIDR range and rules on how traffic can flow. Its CIDR range has to be a subset of the VPC's, for example &lt;code&gt;10.0.1.0/24&lt;/code&gt; which would allow for IPs from &lt;code&gt;10.0.1.0&lt;/code&gt; to &lt;code&gt;10.0.1.255&lt;/code&gt; giving 256 possible IP addresses.&lt;/p&gt;

&lt;p&gt;Subnets are often denominated as 'public' or 'private' depending on whether traffic can reach them from outside the VPC (the Internet). This visibility is controlled by the traffic routing rules and each subnet can have its own rules.&lt;/p&gt;

&lt;p&gt;A subnet has to be in a specific availability zone within a region so it's good practice to have a subnet in each zone. If you plan to have public and private subnets then there should be one of each per availability zone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------+
|            VPC            |
|                           |
+------------+ +------------+
||  Subnet 1 | |  Subnet 2 ||
||10.0.1.0/24| |10.0.2.0/24||
||           | |           ||
|------------+ +------------|
+---------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Availability Zones
&lt;/h4&gt;

&lt;p&gt;We've said that there should be subnets per availability zone, but what does that actually mean?&lt;/p&gt;

&lt;p&gt;Each AWS region is divided into 2 or more different zones which, between them, aim to guarantee a very high level of availability for that region. Essentially, at least one zone should be able to operate, even if others suffer outages (🔥).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------+          +----------+
|us-ea)t-1a|          |us-east-1b|
|_____(____|          |__________|
|     )    |          |          |
|   ( &amp;amp;()  |          |    ✔     |
|  ) () &amp;amp;( |          |    8-)   |
+----------+          +----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Routing Tables
&lt;/h3&gt;

&lt;p&gt;A routing table contains rules about how IP packets in the subnets can travel to different IP addresses. There is always a default route table which will only allow traffic to travel locally, within the VPC. If a subnet has no routing table associated with it then it uses the default one. These would be 'private' subnets.&lt;/p&gt;

&lt;p&gt;If you want external traffic to be able to get to a subnet then you need to create a routing table with a rule explicitly allowing this. Subnets associated to that routing table would be 'public'.&lt;/p&gt;

&lt;p&gt;All of the subnets in the default VPCs are associated with a route table which makes them public.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internet Gateways
&lt;/h3&gt;

&lt;p&gt;The routing table which makes a subnet public needs to reference an Internet gateway to allow the flow of external IP packets into and out of the VPC. You create your Internet gateway and then create a rule which says that packets to &lt;code&gt;0.0.0.0/0&lt;/code&gt; - all IP addresses - need to go to there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Route table
         +-------------------+
         | 10.0.0.0/8: local | Requests within the VPC go over local connections.
      +--+ 0.0.0.0/0: ig-123 | Requests to any other IPs go via the Internet Gateway.
      |  |                   |
      |  +-------------------+
      |
      |
+-----+-------+          +-------------+
|  Subnet 1   |          |  Subnet 2   |
| 10.0.1.0/24 |          | 10.0.2.0/24 |
|             |          |             |
|             | 10.0.2.9 |             |
|             +---------&amp;gt;|             |
|             |          |             |
+-------+-----+          +-------------+
        | 8.8.4.4
        |
        |   +--------+
        +--&amp;gt;| ig-123 |
            |        +-----&amp;gt; The Internet
            +--------+

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

&lt;/div&gt;



&lt;h3&gt;
  
  
   NAT Gateways
&lt;/h3&gt;

&lt;p&gt;If you have an EC2 instance in a private subnet - one which doesn't allow traffic from the Internet to reach it - then there's also no way for IP packets to reach the Internet. We need a mechanism for sending those packets out, and then routing the replies correctly. This is called network address translation and is very likely done in your house by your wifi router.&lt;/p&gt;

&lt;p&gt;A NAT gateway is a device which sits in the public subnets, accepts any IP packets bound for the Internet coming from the private subnets, sends those packets on to their destination and then sends the returning packets back to the source.&lt;/p&gt;

&lt;p&gt;It's not necessary to have NAT gateways if you don't intend instances in your private subnets to talk outside if your VPC but if you do need to do that e.g. using an external API, SaaS database etc. then you can simply set up an EC2 instance (might be cheaper, depending on your traffic), configured appropriately, or use an AWS managed NAT gateway resource (will be easier to manage because you won't be doing it).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  +---------------------+
  |   Public Subnet     |
  |   10.0.1.0/24       |
  |                     |
  |    +------------+   |
  |    |            +-----------&amp;gt;   The Internet
  |    |  nat-123   |   |
  |    |            |   |
  |    +-------^----+   |
  |            |        |
  +------------|--------+
               |
               | 8.8.4.4
               |                       Route table
  +------------+---------+    +---------------------+
  |  Private Subnet      +----+  10.0.0.0/16: local |
  |  10.0.20.0/24        |    |  0.0.0.0/0: nat-123 |
  |                      |    +---------------------+
  +----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The public subnet contains the NAT gateway&lt;/li&gt;
&lt;li&gt;A request is made from the private subnet to an IP address somewhere on the Internet&lt;/li&gt;
&lt;li&gt;The route table says that it needs to go to the NAT gateway&lt;/li&gt;
&lt;li&gt;The NAT gateway sends it on&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security Groups
&lt;/h3&gt;

&lt;p&gt;VPC network Security groups denote what traffic can flow to (and from) EC2 instances within your VPC. A security groups can specify ingress (inbound) and egress (outbound) traffic rules, limiting them to certain sources (inbound) and destinations (outbound). They are associated with EC2 instances rather than subnets.&lt;/p&gt;

&lt;p&gt;By default all traffic is allowed out, but no traffic is allowed in. Inbound rules can specify a source address - either a CIDR block or another security group - and a port range. When the source is another security group then that must be within the same VPC. For example, a VPC is created with a default security group which allows traffic from anything which has that same security group. Assigning the group to everything created in the VPC (not necessarily the most secure practice) means that all those resources can talk to each another.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   +---------------+
                   | sg-abcde      |
                   | ALLOW TCP 443 |
                   +----+----------+
                         |
                    +----+------+
                    |  i-67890  |
 10.0.1.123:22      |           | 10.0.1.123:443
------------------&amp;gt;X|           &amp;lt;----------------
                    |           |
                    +-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;An instance (i-67890) has a security group (sg-abcde) which allows TCP traffic on port 443&lt;/li&gt;
&lt;li&gt;A request is made to its IP address (10.0.1.123) on port 22 which doesn't get through&lt;/li&gt;
&lt;li&gt;A request is made to port 443 on the instance and the traffic is allowed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting it All Together
&lt;/h2&gt;

&lt;p&gt;The complete picture of your virtual private network looks something like the picture below, with public and private subnets spread across availability zones, network address translation sitting in the public subnets and route tables to specify how packets are routed. EC2 instances are run in any subnet and have security groups attached to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                                        +-------+                                  
                                        | ig-1  |                                  
                                        |       |                                  
        vpc-123: 10.0.0.0/16  |         |       |        |                         
       +----------------------+---------+-------+--------+---------------------+
       |                      |                          |                     |   
       |  +-----+             |  +-----+                 |  +-----+            |   
       |  | NAT |             |  | NAT |                 |  | NAT |            |   
public |  |     |             |  |     |                 |  |     |            |   
subnets|  +-----+             |  +-----+                 |  +-----+            |   
       |                      |                          |                     |   
       |                      |                          |                     |   
       |                      |                          |                     |   
       |              +-------+                  +-------+             +-------+
       |              | rt-1a |                  | rt-1b |             | rt-1c |
       | 10.0.1.0/24  |       | 10.0.2.0/24      |       | 10.0.3.0/24 |       |   
-------+-----------------------------------------------------------------------+
       | 10.0.4.0/24  | rt-2a | 10.0.5.0/24      | rt-2b | 10.0.6.0/24 | rt-2c |
       |              |       |                  |       |             |       |   
       |              +-------+                  +-------+             +-------+
private|                      |                          |                     |   
subnets|                      |                          |                     |   
       |                      |                          |                     |   
       |                      |                          |                     |   
       |                      |                          |                     |   
       |                      |                          |                     |   
       |                      |                          |                     |   
       |                      |                          |                     |   
       +----------------------+--------------------------+---------------------+
       |         AZ 1         |          AZ 2            |        AZ 3         |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>networking</category>
    </item>
    <item>
      <title>The Quickest Way to Run Python in Docker</title>
      <dc:creator>Graham Lyons</dc:creator>
      <pubDate>Wed, 15 Nov 2017 16:41:13 +0000</pubDate>
      <link>https://dev.to/grahamlyons/the-quickest-way-to-run-python-in-docker-165</link>
      <guid>https://dev.to/grahamlyons/the-quickest-way-to-run-python-in-docker-165</guid>
      <description>&lt;p&gt;I love Python. I think it's a beautifully designed language with a philosophy that I really appreciate as a developer trying to get stuff done. Just run this at a command prompt: &lt;code&gt;python -m this&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's not so nice is Python packaging. It's awkward, it's confusing - it's getting better but it's still not nearly as nice as it is in other languages (Ruby, Java, Javascript).&lt;/p&gt;

&lt;p&gt;Docker is a collection of various Linux features  - namespaces, cgroups, union file-system - put together in such a way that you can package and distribute software in a language-agnostic container. Docker is a great way to skirt the pain of Python packaging.&lt;/p&gt;

&lt;p&gt;To install it, go to &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;https://www.docker.com/&lt;/a&gt; and under the "Get Docker" link choose the version for your operating system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just Enough Docker
&lt;/h2&gt;

&lt;p&gt;So. What's the least we can get away with? Or, what's the least I can write to illustrate this? Well, if we use the &lt;code&gt;onbuild&lt;/code&gt; Python Docker image, then not much.&lt;/p&gt;

&lt;p&gt;Imagine we have a super-simple Flask app which just has one route, returning a fixed string (Hello, World?). We need very little, but we do have the dependency on Flask. Even on my Mac, with the latest OS (OK, not High Sierra just yet), the default Python installation doesn't include the &lt;code&gt;pip&lt;/code&gt; package manager. What the hell? It does include &lt;code&gt;easy_install&lt;/code&gt;, so I could &lt;code&gt;easy_install pip&lt;/code&gt;, or rather &lt;code&gt;sudo easy_install pip&lt;/code&gt; because it'll go in a global location. Then I could globally install the &lt;code&gt;flask&lt;/code&gt; package too. Woop-de-doo. Which version is now globally installed on my system? Who knows!(?)&lt;/p&gt;

&lt;p&gt;Let's not do that. Let's create our &lt;code&gt;requirements.txt&lt;/code&gt; file:&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;echo &lt;/span&gt;Flask &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our Flask app:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;em&gt;then&lt;/em&gt; let's have a &lt;code&gt;Dockerfile&lt;/code&gt; too:&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;echo &lt;/span&gt;FROM python:onbuild &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build It
&lt;/h2&gt;

&lt;p&gt;OK, so let's build 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="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp.local &lt;span class="nb"&gt;.&lt;/span&gt;
Sending build context to Docker daemon  4.096kB
Step 1/1 : FROM python:onbuild
&lt;span class="c"&gt;# Executing 3 build triggers...&lt;/span&gt;
Step 1/1 : COPY requirements.txt /usr/src/app/
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
Step 1/1 : RUN pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
Step 1/1 : COPY &lt;span class="nb"&gt;.&lt;/span&gt; /usr/src/app
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 79fdf87107de
Successfully built 79fdf87107de
Successfully tagged myapp.local:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Was that it? Is it built? Yup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image &lt;span class="nb"&gt;ls &lt;/span&gt;myapp.local
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myapp.local         latest              c58ad169cb28        4 seconds ago       700MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;

&lt;p&gt;Great, our app is inside a container! What next? Run the container like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; myapp.local python server.py
 &lt;span class="k"&gt;*&lt;/span&gt; Running on http://0.0.0.0:5000/ &lt;span class="o"&gt;(&lt;/span&gt;Press CTRL+C to quit&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That tells the &lt;code&gt;docker&lt;/code&gt; command to &lt;code&gt;run&lt;/code&gt; the &lt;code&gt;myapp.local&lt;/code&gt; container (which we built), to remove it when it stops (&lt;code&gt;--rm&lt;/code&gt;) and to run the command &lt;code&gt;python server.py&lt;/code&gt; inside it. Amazing! So can we see our app now?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Piece
&lt;/h2&gt;

&lt;p&gt;We can't see our app at the moment because although it's running perfectly inside the container it's not accessible anywhere else. The message we get when we run it says it's listening on port &lt;code&gt;5000&lt;/code&gt;, but if we try to access that on the same host we get an error:&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;curl localhost:5000
curl: &lt;span class="o"&gt;(&lt;/span&gt;7&lt;span class="o"&gt;)&lt;/span&gt; Failed connect to localhost:5000&lt;span class="p"&gt;;&lt;/span&gt; Connection refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to expose the port outside the container that it's running in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5001:5000 myapp.local python server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-p&lt;/code&gt; argument maps your local &lt;code&gt;5001&lt;/code&gt; port to &lt;code&gt;5000&lt;/code&gt; inside the container (they can just be the same but I've made them different just to illustrate where the host and container ones are).&lt;/p&gt;

&lt;h2&gt;
  
  
   OK, not quite the end...
&lt;/h2&gt;

&lt;p&gt;So I found out when I was writing this that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The ONBUILD image variants are deprecated, and their usage is discouraged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's OK - you wouldn't really use the &lt;code&gt;ONBUILD&lt;/code&gt; image for anything serious, and the &lt;code&gt;Dockerfile&lt;/code&gt; that defines it is very easy to understand. Check it out for yourself and see how it works: &lt;a href="https://github.com/docker-library/python/blob/f12c2d/3.6/jessie/onbuild/Dockerfile" rel="noopener noreferrer"&gt;https://github.com/docker-library/python/blob/f12c2d/3.6/jessie/onbuild/Dockerfile&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can replace the contents of our &lt;code&gt;Dockerfile&lt;/code&gt; with the following and get the same result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /usr/src/app
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt /usr/src/app/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /usr/src/app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've just removed the &lt;code&gt;ONBUILD&lt;/code&gt; directives (plus the &lt;code&gt;3.6-jessie&lt;/code&gt; Python version - we can just take the latest).&lt;/p&gt;

&lt;h2&gt;
  
  
  Really The End
&lt;/h2&gt;

&lt;p&gt;So that's our minimal example of how to run a Python app with its dependencies inside a Docker container. Docker is an amazing piece of technology and it's no surprise that the &lt;a href="https://www.bloomberg.com/news/articles/2017-08-09/docker-is-said-to-be-raising-funding-at-1-3-billion-valuation" rel="noopener noreferrer"&gt;company&lt;/a&gt; is &lt;a href="https://www.sdxcentral.com/articles/news/sources-microsoft-tried-to-buy-docker-for-4b/2016/06/" rel="noopener noreferrer"&gt;valued&lt;/a&gt; so &lt;a href="https://www.forbes.com/sites/mikekavis/2015/07/16/5-reasons-why-docker-is-a-billion-dollar-company/#47e077c1f04f" rel="noopener noreferrer"&gt;highly&lt;/a&gt;. In this instance it provides a clean way for us to avoid pain with Python packaging, but we now also have a container image with our app inside which could be distributed and run on any other system which can run Docker.&lt;/p&gt;

</description>
      <category>python</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
