<?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>Forem: Nate Vick</title>
    <description>The latest articles on Forem by Nate Vick (@natevick).</description>
    <link>https://forem.com/natevick</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%2F68297%2F07ad8bb0-55f4-4a15-8ba1-09642de2e6aa.jpeg</url>
      <title>Forem: Nate Vick</title>
      <link>https://forem.com/natevick</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/natevick"/>
    <language>en</language>
    <item>
      <title>Deploy Infrastructure With CDKTF</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Tue, 07 Sep 2021 20:19:54 +0000</pubDate>
      <link>https://forem.com/hint/deploy-infrastructure-with-cdktf-2202</link>
      <guid>https://forem.com/hint/deploy-infrastructure-with-cdktf-2202</guid>
      <description>&lt;p&gt;CDK or Cloud Development Kit came out of AWS in 2018 as a way to write Infrastructure as Code in software languages used day-to-day by developers (JavaScript/TypeScript, Python, Java, C#, and soon Go). Since its release, a community has built up around it, and new flavors have arrived. AWS CDK, CDKTF, and CDK8s are all based on the same core, but compile to different formats. AWS CDK compiles to CloudFormation, while CDKTF compiles to Terraform compatible JSON, and CDK8s compiles to Kubernetes config.&lt;/p&gt;

&lt;p&gt;In this post, we will walk through how to use CDKTF with DigitalOcean's Terraform provider. A Terraform provider is a "plugin" for Terraform to interact with remote systems. In this case, the provider is created and maintained by DigitalOcean. We will share a few examples of creating a Digital Ocean Project, a VPC, a Postgres Database, and a Droplet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prereqs and Install
&lt;/h2&gt;

&lt;p&gt;To use cdktf you will need the following packages installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform ≥ 0.12&lt;/li&gt;
&lt;li&gt;Node.js ≥ 12.16&lt;/li&gt;
&lt;li&gt;Yarn ≥ 1.21&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To install cdktf, we will use &lt;code&gt;npm install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ npm install --global cdktf-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You will also need to &lt;a href="https://docs.digitalocean.com/reference/api/create-personal-access-token/" rel="noopener noreferrer"&gt;create a personal access token&lt;/a&gt; on Digital Ocean if you would like to deploy this config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create and Initialize the example project
&lt;/h2&gt;

&lt;p&gt;First let's make a directory and change into it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ mkdir cdk-do-example &amp;amp;&amp;amp; cd cdk-do-example&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Initialize the project with the &lt;code&gt;init&lt;/code&gt; command. In general, I use the &lt;code&gt;--local&lt;/code&gt; flag, so all state is stored locally.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ cdktf init --template=typescript --local&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You will be prompted for Project Name and Description but defaults are fine.&lt;/p&gt;

&lt;p&gt;The last step of setting up the project is to add Terraform providers to the &lt;code&gt;cdktf.json&lt;/code&gt;. Open the project in your editor and let's add the DigitalOcean provider to it as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "language": "typescript",
  "app": "npm run --silent compile &amp;amp;&amp;amp; node main.js",
  "terraformProviders": [
    "digitalocean/digitalocean@~&amp;gt; 2.9"
  ],
  "terraformModules": [],
  "context": {
    "excludeStackIdFromLogicalIds": "true",
    "allowSepCharsInLogicalIds": "true"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Dependencies
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;cdktf get&lt;/code&gt; to download the dependencies for using DigitalOcean with Typescript.&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;cdktf get
Generated typescript constructs &lt;span class="k"&gt;in &lt;/span&gt;the output directory: .gen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's Write Some TypeScript
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;main.ts&lt;/code&gt; in your editor and let's start by creating a DO Project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider } from './.gen/providers/digitalocean'
import { Project } from './.gen/providers/digitalocean'

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name)

    new DigitaloceanProvider(this, 'digitalocean', {
      token: Token.asString(process.env.DO_TOKEN) 
    })

    new Project(this, 'example-project', {
      name: 'Example Rails Project'
    })

  }
}

const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will create a new &lt;code&gt;DigitaloceanProvider&lt;/code&gt; and pass in the environment variable assigned to your DO personal access token. Next, we create the &lt;code&gt;Project&lt;/code&gt;, which has a required key of &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Optionally, as a test of the code above, run &lt;code&gt;cdktf deploy&lt;/code&gt; to deploy your CDKTF project. The cli will ask if you want to make the changes listed under Resources. Type 'yes', then once it finishes, you will see a green check next to the Resources it successfully created.&lt;/p&gt;

&lt;p&gt;cli prompt:&lt;br&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%2Fgjekjujx94rx8dkjzjki.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%2Fgjekjujx94rx8dkjzjki.png" alt="Agree to deployment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;successful deployment:&lt;br&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%2Fufzx6hnfwgnv6xmvdkkf.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%2Fufzx6hnfwgnv6xmvdkkf.png" alt="First deployment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we will add a VPC, a Postgres Database, and a Droplet. Once those examples are in, we will tie it all together before deploying it again.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a VPC
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider, Droplet, Vpc } from './.gen/providers/digitalocean'
import { Project } from './.gen/providers/digitalocean'

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name)

    new DigitaloceanProvider(this, 'digitalocean', {
      token: Token.asString(process.env.DO_TOKEN) 
    })
    ...more code above

    new Vpc(this, 'example-vpc', {
      name: 'example-vpc',
      region: 'sfo3'
    })

  }
}

const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;*Note: If you do not have VPC in your account already this will become the default VPC which will not be deleted when cleaning up the CDKTF Project.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a Postgres Database
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider, Droplet, Vpc, DatabaseCluster, DatabaseUser, DatabaseDb } from './.gen/providers/digitalocean'
import { Project } from './.gen/providers/digitalocean'

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name)

    new DigitaloceanProvider(this, 'digitalocean', {
      token: Token.asString(process.env.DO_TOKEN) 
    })
    ...more code above

    const postgres = new DatabaseCluster(this, 'example-postgres', {
      name: 'example-postgres',
      engine: 'pg',
      version: '13',
      size: 'db-s-1vcpu-1gb',
      region: 'sfo3',
      nodeCount: 1
    })

    new DatabaseUser(this, 'example-postgres-user', {
      clusterId: `${postgres.id}`,
      name: 'example'
    })

    new DatabaseDb(this, 'example-postgres-db', {
      clusterId: `${postgres.id}`,
      name: 'example-db'
    })

  }
}

const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice we assigned the &lt;code&gt;DatabaseCluster&lt;/code&gt; to a variable &lt;code&gt;const postgres&lt;/code&gt;. We then use the variable to create the &lt;code&gt;DatabaseUser&lt;/code&gt; and &lt;code&gt;DatabaseDb&lt;/code&gt; on that cluster.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a Droplet
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider, Droplet, Vpc, DatabaseCluster, DatabaseUser, DatabaseDb } from './.gen/providers/digitalocean'
import { Project } from './.gen/providers/digitalocean'

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name)

    new DigitaloceanProvider(this, 'digitalocean', {
      token: Token.asString(process.env.DO_TOKEN) 
    })
    ...more code above

    new Droplet(this, 'example-droplet', {
      name: 'example-droplet',
      size: 's-1vcpu-1gb',
      region: 'sfo3',
      image: 'ubuntu-20-04-x64'
    })

  }
}

const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Let's Put It All Together
&lt;/h2&gt;

&lt;p&gt;If you run &lt;code&gt;cdktf deploy&lt;/code&gt; now, it would create everything, but nothing created would be put into the Digital Ocean project or the VPC we create. Let's do that now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider, Droplet, Vpc} from './.gen/providers/digitalocean'
import { DatabaseCluster, DatabaseUser, DatabaseDb } from './.gen/providers/digitalocean'
import { Project, ProjectResources } from './.gen/providers/digitalocean'

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name)

    new DigitaloceanProvider(this, 'digitalocean', {
      token: Token.asString(process.env.DO_TOKEN) 
    })

    const project = new Project(this, 'example-project', {
      name: 'Example Rails Project'
    })

    const vpc = new Vpc(this, 'example-vpc', {
      name: 'example-vpc',
      region: 'sfo3'
    })

    const postgres = new DatabaseCluster(this, 'example-postgres', {
      name: 'example-postgres',
      engine: 'pg',
      version: '13',
      size: 'db-s-1vcpu-1gb',
      region: 'sfo3',
      nodeCount: 1,
      privateNetworkUuid: vpc.id
    })

    new DatabaseUser(this, 'example-postgres-user', {
      clusterId: `${postgres.id}`,
      name: 'example'
    })

    new DatabaseDb(this, 'example-postgres-db', {
      clusterId: `${postgres.id}`,
      name: 'example-db'
    })

    const droplet = new Droplet(this, 'example-droplet', {
      name: 'example-droplet',
      size: 's-1vcpu-1gb',
      region: 'sfo3',
      image: 'ubuntu-20-04-x64',
      vpcUuid: vpc.id
    })

    new ProjectResources(this, 'example-project-resources', {
      project: project.id,
      resources: [
        postgres.urn,
        droplet.urn
      ],
      dependsOn: [ postgres, droplet ]
    })
  }
}

const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by assigning the project, VPC, and droplet to variables. In the &lt;code&gt;DatabaseCluster&lt;/code&gt; definition, we add &lt;code&gt;privateNetworkUuid: [vpc.id](http://vpc.id)&lt;/code&gt; to place the database in our newly created VPC. Similarly, on the &lt;code&gt;Droplet&lt;/code&gt; definition, we place it in the VPC, by adding &lt;code&gt;vpcUuid: vpc.id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, create a new &lt;code&gt;ProjectResource&lt;/code&gt; to assign other resources to the Digital Ocean project. In this small example, we will assign the database and droplet to the project using the &lt;code&gt;urn&lt;/code&gt; for each resource. We will wait to assign those until both are created using a Terraform helper &lt;code&gt;dependsOn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With all of that in place you can deploy again. The database and droplet creation take a bit, so be patient. 🙂 Once it has finished, check your Digital Ocean Dashboard to see everything created and ready for use.&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%2Fctax9tuohfejr36iezuu.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%2Fctax9tuohfejr36iezuu.png" alt="Full Example Deployed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;*Make sure you run &lt;code&gt;cdktf destroy&lt;/code&gt; to remove these resources from your account or you will be charged by Digital Ocean.*&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things To Know
&lt;/h2&gt;

&lt;p&gt;CDKTF is still a young project, so it is changing fast, has limited documentation, and you can run into unclear errors.&lt;/p&gt;

&lt;p&gt;There are few things that help with the limited documentation. You can read the provider documentation on the &lt;a href="https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs" rel="noopener noreferrer"&gt;Terraform registry site&lt;/a&gt; and use it as a guide. Also, using a language like Typescript with VSCode there are a lot of code hints, hover info, and signature information. The gif below is an example of what is shown in VSCode when you hover on the problem. Note the missing properties for &lt;code&gt;DropletConfig&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/Ae5Z300UKAWhqZiHfc/source.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/Ae5Z300UKAWhqZiHfc/source.gif" alt="Typescript Annotations"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you run into unclear errors, prepend &lt;code&gt;CDKTF_LOG_LEVEL=debug&lt;/code&gt; to the deploy and/or destroy commands to get very verbose output.&lt;/p&gt;

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

&lt;p&gt;In this post, we used CDKTF to create some basic example resources on Digital Ocean which gives you a good primer to build more complex infrastructure in a language of your choice. You can find the code from this post in this &lt;a href="https://github.com/hintmedia/cdk-do-example" rel="noopener noreferrer"&gt;repo&lt;/a&gt;. If you would like to chat more about CDK or infrastructure as code you can ping me on Twitter &lt;a href="https://twitter.com/natron99" rel="noopener noreferrer"&gt;@natron99&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>Happy Ruby 3 Release Day!!!</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Fri, 25 Dec 2020 14:02:52 +0000</pubDate>
      <link>https://forem.com/natevick/happy-ruby-3-release-day-ja1</link>
      <guid>https://forem.com/natevick/happy-ruby-3-release-day-ja1</guid>
      <description>&lt;p&gt;&lt;a href="https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/"&gt;https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, Merry Christmas 🎄 &lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Rails System Tests In Docker</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Tue, 28 Apr 2020 17:04:35 +0000</pubDate>
      <link>https://forem.com/hint/rails-system-tests-in-docker-4cj1</link>
      <guid>https://forem.com/hint/rails-system-tests-in-docker-4cj1</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://hint.io/blog/rails-system-test-docker"&gt;Hint's blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At Hint, we use Docker extensively. It is our development environment for all of our projects. On a recent greenfield project, we wanted to use Rails &lt;a href="https://weblog.rubyonrails.org/2017/4/27/Rails-5-1-final/"&gt;System Tests&lt;/a&gt; to validate the system from end to end. &lt;/p&gt;

&lt;p&gt;In order to comfortably use System Tests inside of Docker we had to ask ourselves a few questions: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How do we use RSpec for System Tests?&lt;/li&gt;
&lt;li&gt;How do we run the tests in headless mode in a modern browser?&lt;/li&gt;
&lt;li&gt;Can we run the test in a non-headless browser for building and debugging efficiently?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;In the context of answering these questions, we are going to first need to profile the Rails project to which these answers apply. Our Rails app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses Docker and Docker Compose.&lt;/li&gt;
&lt;li&gt;Uses RSpec for general testing.&lt;/li&gt;
&lt;li&gt;Has an entrypoint or startup script.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If some or none of the above apply to your app and you would like to learn more about our approach to Docker, take a look at this post: &lt;a href="https://hint.io/blog/rails-development-with-docker"&gt;Dockerizing a Rails Project&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prep Work
&lt;/h2&gt;

&lt;p&gt;If your project was started before Rails 5.1, some codebase preparation is necessary. Beginning in Rails 5.1, the Rails core team integrated Capybara meaning Rails now properly handles all the painful parts of full system tests, e.g. database rollback. This means tools like &lt;code&gt;database_cleaner&lt;/code&gt; are no longer needed. If it is present, remove &lt;code&gt;database_cleaner&lt;/code&gt; from your Gemfile and remove any related config (typically found in &lt;code&gt;spec/support/database_cleaner.rb&lt;/code&gt; , &lt;code&gt;spec/spec_helper.rb&lt;/code&gt; , or &lt;code&gt;spec/rails_helper.rb&lt;/code&gt;).&lt;/p&gt;

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

&lt;p&gt;After that codebase prep has been completed, verify that RSpec ≥ 3.7 and the required system tests helper gems are installed.&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;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rspec-rails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 3.7'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'capybara'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 2.15'&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'selenium-webdriver'&lt;/span&gt;
  &lt;span class="c1"&gt;# gem 'chromedriver-helper' don't leave this cruft in your Gemfile.:D&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the default system test helper gems installed with &lt;code&gt;rails new&lt;/code&gt; after Rails 5.1. We will not need &lt;code&gt;chromedriver-helper&lt;/code&gt; since we will be using a separate container for headless Chrome with &lt;code&gt;chromedriver&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The configuration below has been tested on Mac and Linux, but not Windows.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;Speaking of containers, let's add that service to the &lt;code&gt;docker-compose.yml&lt;/code&gt; configuration file.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# other services...&lt;/span&gt;
  &lt;span class="na"&gt;selenium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selenium/standalone-chrome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have added the &lt;code&gt;selenium&lt;/code&gt; service, which pulls down the latest &lt;code&gt;selenium/standalone-chrome&lt;/code&gt; image. You may notice I have not mapped a port to the host. This service is for inter-container communication, so there is no reason to map a port. We will need to add an environment variable to the service (app in this case) that Rails/RSpec will be running on for setting a portion of the Selenium URL.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rails server -p 3000 -b '0.0.0.0'&lt;/span&gt;
    &lt;span class="c1"&gt;# ... more config ...&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;43447:43447"&lt;/span&gt;
    &lt;span class="c1"&gt;# ... more config ...&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SELENIUM_REMOTE_HOST=selenium&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Capybara
&lt;/h2&gt;

&lt;p&gt;We added a port mapping for Capybara as well: &lt;code&gt;43447:43447&lt;/code&gt;. Now let's add the Capybara config at &lt;code&gt;spec/support/capybara.rb&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&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="n"&gt;headless&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'HEADLESS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'false'&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;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:rack_test&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;before&lt;/span&gt; &lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;headless&lt;/span&gt;
            &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SELENIUM_REMOTE_HOST'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:4444/wd/hub"&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="s1"&gt;'http://host.docker.internal:9515'&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:selenium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :chrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;options: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;browser:              :remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;url:                  &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;desired_capabilities: :chrome&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Find Docker IP address&lt;/span&gt;
    &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;headless&lt;/span&gt;
                             &lt;span class="sb"&gt;`/sbin/ip route|awk '/scope/ { print $9 }'`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
                           &lt;span class="k"&gt;else&lt;/span&gt;
                             &lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;
                           &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'43447'&lt;/span&gt;
    &lt;span class="n"&gt;session_server&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;
    &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_host&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;session_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;session_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;after&lt;/span&gt; &lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&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;log&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/This page includes a password or credit card input in a non-secure context/&lt;/span&gt;
          &lt;span class="c1"&gt;# Ignore this warning in tests&lt;/span&gt;
          &lt;span class="k"&gt;next&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
          &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;message&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break it down section by section.&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;headless&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'HEADLESS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using the &lt;code&gt;headless&lt;/code&gt; variable to make some decisions later in the file. This variable allows us to run &lt;code&gt;bundle exec rspec&lt;/code&gt; normally and run system tests against headless Chrome in the &lt;code&gt;selenium&lt;/code&gt; container. Or we run &lt;code&gt;HEADLESS=false bundle exec rspec&lt;/code&gt; and when a system test will attempt to connect to &lt;code&gt;chromedriver&lt;/code&gt; running on the host 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="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt; &lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:rack_test&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our default driver for system tests will be &lt;code&gt;rack_test&lt;/code&gt;. It is the fastest driver available because it does not involve starting up a browser. It also means we cannot test JavaScript while using it, which brings us to the next section.&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt; &lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;headless&lt;/span&gt;
          &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SELENIUM_REMOTE_HOST'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:4444/wd/hub"&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="s1"&gt;'http://host.docker.internal:9515'&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:selenium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :chrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;options: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;browser:              :remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;url:                  &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;desired_capabilities: :chrome&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# ...more config...       &lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any specs with &lt;code&gt;js: true&lt;/code&gt; set will use this config. We set the &lt;code&gt;url&lt;/code&gt; for Selenium to use depending on if we are running headless or not. Notice the special Docker domain we are setting the non-headless url to; it is a URL that points to the host machine. The special domain is currently only available on Mac and Windows, so we will need to handle that for Linux later.&lt;/p&gt;

&lt;p&gt;We set our driver to &lt;code&gt;:selenium&lt;/code&gt; with config options for &lt;code&gt;browser, url, desired_capabilities&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt; &lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="c1"&gt;# ...selenium config...&lt;/span&gt;

  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;headless&lt;/span&gt;
                           &lt;span class="sb"&gt;`/sbin/ip route|awk '/scope/ { print $9 }'`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
                         &lt;span class="k"&gt;else&lt;/span&gt;
                           &lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;
                         &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'43447'&lt;/span&gt;
  &lt;span class="n"&gt;session_server&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;
  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_host&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;session_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;session_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we set the &lt;code&gt;Capybara.server_host&lt;/code&gt; address to the &lt;code&gt;app&lt;/code&gt; container IP address if headless or &lt;code&gt;0.0.0.0&lt;/code&gt; if not.&lt;/p&gt;

&lt;p&gt;The last part of RSpec configuration is to require this config in &lt;code&gt;spec/rails_helper.rb&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# This file is copied to spec/ when you run 'rails generate rspec:install'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'spec_helper'&lt;/span&gt;
&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RAILS_ENV'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../../config/environment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Prevent database truncation if the environment is production&lt;/span&gt;
&lt;span class="nb"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"The Rails environment is running in production mode!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rspec/rails'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'support/capybara'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to install &lt;a href="https://sites.google.com/a/chromium.org/chromedriver/downloads"&gt;ChromeDriver&lt;/a&gt; on the host machine. You will need to place it in a location in your &lt;code&gt;$PATH&lt;/code&gt;. Once it is there, when you want to run non-headless system tests, you will need to start ChromeDriver &lt;code&gt;chromedriver --whitelisted-ips&lt;/code&gt; in a new terminal session.  Now on a Mac, you should be able to run headless or non-headless system tests. Those commands again are:&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;#HEADLESS&lt;/span&gt;
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec

&lt;span class="c"&gt;#NON-HEADLESS&lt;/span&gt;
&lt;span class="nv"&gt;HEADLESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Special Linux Config
&lt;/h2&gt;

&lt;p&gt;There is one last step for Linux users because of the special &lt;code&gt;host.docker.internal&lt;/code&gt; URL is not available. We need to add some config to the entrypoint or startup script to solve that issue.&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="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_DOMAIN&lt;/span&gt;:&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"host.docker.internal"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;check_host &lt;span class="o"&gt;{&lt;/span&gt; ping &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-c1&lt;/span&gt; &lt;span class="nv"&gt;$HOST_DOMAIN&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# check if the docker host is running on mac or windows&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; check_host&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;HOST_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ip route | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR==1 {print $3}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOST_IP&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HOST_DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/hosts
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We set an environment variable to the special Docker URL. We then create a function to check if the host responds to that URL. If it responds, we move on assuming we are running on Mac or Windows. If it does not respond, we assign the container's IP to an environment variable, then append a record to &lt;code&gt;/etc/hosts&lt;/code&gt;. We are now all set to run system tests on Linux as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: CI Setup
&lt;/h2&gt;

&lt;p&gt;Let's wrap this up with config to run system tests on Circle CI. We need to add &lt;code&gt;SELENIUM_REMOTE_HOST&lt;/code&gt; and the Selenium Docker image to &lt;code&gt;.circleci/config.yml&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="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;parallelism&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;circleci/ruby:2.6.0-node&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SELENIUM_REMOTE_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selenium/standalone-chrome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect with me on Twitter(&lt;a href="https://twitter.com/natron99"&gt;@natron99&lt;/a&gt;) to continue the conversation about Rails and Docker!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>docker</category>
      <category>ruby</category>
    </item>
    <item>
      <title>ActiveStorage &amp; S3 Server-side Encryption</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Thu, 26 Mar 2020 20:46:46 +0000</pubDate>
      <link>https://forem.com/hint/activestorage-s3-server-side-encryption-21nk</link>
      <guid>https://forem.com/hint/activestorage-s3-server-side-encryption-21nk</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://hint.io/blog/activestorage-s3-sse"&gt;Hint's blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;TIL, it is possible to use S3 server-side encryption and ActiveStorage.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://github.com/rails/rails/commit/32331b19e1da8bdab3c9f6d1666ac2d3108e5042"&gt;commit to Rails&lt;/a&gt; in 2017 adds the ability but did not add documentation or an example of how to use the &lt;code&gt;upload_options&lt;/code&gt; feature. Below is a vanilla S3 service config for ActiveStorage.&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;amazon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S3&lt;/span&gt;
  &lt;span class="na"&gt;access_key_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ACCESS_KEY_ID&lt;/span&gt;
  &lt;span class="na"&gt;secret_access_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SECRET_ACCESS_KEY&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BUCKET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a S3 service config using &lt;code&gt;upload&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="na"&gt;amazon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S3&lt;/span&gt;
  &lt;span class="na"&gt;access_key_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ACCESS_KEY_ID&lt;/span&gt;
  &lt;span class="na"&gt;secret_access_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SECRET_ACCESS_KEY&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BUCKET&lt;/span&gt;
  &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;server_side_encryption&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aws:kms'&lt;/span&gt; &lt;span class="c1"&gt;# 'AES256'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;upload&lt;/code&gt; hash is passed to &lt;code&gt;Aws::S3::Client#put_object(params = {})&lt;/code&gt;. One of the configuration options for &lt;code&gt;put_object&lt;/code&gt; is &lt;code&gt;:server_side_encryption (String)&lt;/code&gt;. For more options checkout the &lt;a href="https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_object-instance_method"&gt;Ruby SDK docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;💡If you are using KMS keys, the bucket user will need the following policies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"kms:Decrypt"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"kms:Encrypt"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"kms:GenerateDataKey"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"kms:ReEncryptTo"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"kms:DescribeKey"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"kms:ReEncryptFrom"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To help other Rails devs, here is a &lt;a href="https://github.com/rails/rails/pull/38801"&gt;PR to Rails&lt;/a&gt; to add the above example to the official guides.&lt;/p&gt;

&lt;p&gt;Have a great day!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>showdev</category>
      <category>aws</category>
    </item>
    <item>
      <title>Happy Ruby Day!!!</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Wed, 25 Dec 2019 17:26:53 +0000</pubDate>
      <link>https://forem.com/natevick/happy-ruby-day-117g</link>
      <guid>https://forem.com/natevick/happy-ruby-day-117g</guid>
      <description>&lt;p&gt;&lt;a href="https://www.ruby-lang.org/en/news/2019/12/25/ruby-2-7-0-released/"&gt;Ruby 2.7.0 Released&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Introducing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pattern Matching&lt;/li&gt;
&lt;li&gt;REPL (irb) improvement&lt;/li&gt;
&lt;li&gt;Compaction GC&lt;/li&gt;
&lt;li&gt;Separation of positional and keyword arguments
and more!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>What are you doing this weekend?</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Sun, 22 Dec 2019 16:32:27 +0000</pubDate>
      <link>https://forem.com/natevick/what-are-you-doing-this-weekend-4ibn</link>
      <guid>https://forem.com/natevick/what-are-you-doing-this-weekend-4ibn</guid>
      <description>&lt;p&gt;I went for a drive yesterday around Mt St Helens. I couldn’t see the mountain, but I saw a bunch of waterfalls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZdpC_2lX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/qybszzukr4fkyirn969l.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZdpC_2lX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/qybszzukr4fkyirn969l.jpeg" alt="Alt Text" width="612" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Guide: Rails Development with Docker</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Tue, 17 Dec 2019 20:18:32 +0000</pubDate>
      <link>https://forem.com/hint/rails-development-with-docker-13np</link>
      <guid>https://forem.com/hint/rails-development-with-docker-13np</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://hint.io/blog/rails-development-with-docker"&gt;Hint's blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a software consultancy, we switch between many projects throughout the year. A critical factor in delivering value is the ease at which we are able to move between projects.&lt;/p&gt;

&lt;p&gt;Over the years, we have used many tools to manage dependencies needed to run and develop our clients' projects. The problem with most tools has been the ability to have consistent, reproducible development environments across our team. About two years ago, we discovered that Docker was a viable option for building consistent development environments. Since then, we continue to iterate on our configuration as we learn new ways to handle the complexity of the projects while simplifying the setup process for our team.&lt;/p&gt;

&lt;p&gt;In this guide, we will cover the basics of our Docker development environment for Rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;If you would like to follow along, &lt;a href="https://hub.docker.com/search/?type=edition&amp;amp;offering=community"&gt;install Docker CE&lt;/a&gt; and create, &lt;a href="https://github.com/hintmedia/base-rails-app"&gt;clone&lt;/a&gt;, or have a working Rails app.&lt;/p&gt;

&lt;p&gt;We will be using a combination of Dockerfiles, Docker Compose, and bash scripts throughout this guide, so let's make a place for most of those files to live.&lt;br&gt;
Start by creating a &lt;code&gt;docker&lt;/code&gt; folder in the root of the Rails project. Here we will store Dockerfiles and bash scripts to be referenced from the &lt;code&gt;docker-compose.yml&lt;/code&gt; file we will be creating later.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;docker&lt;/code&gt; folder, create another folder named &lt;code&gt;ruby&lt;/code&gt;. Inside the newly created &lt;code&gt;ruby&lt;/code&gt; folder, create a file named &lt;code&gt;Dockerfile&lt;/code&gt;. This file will contain commands to build a custom image for your Rails app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_o8eh-Tv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/6gzhijjsx37uumq7cy61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_o8eh-Tv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/6gzhijjsx37uumq7cy61.png" alt="initial Docker directories" width="241" height="96"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Hello Dockerfile
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  ARG &lt;span class="nv"&gt;RUBY_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.6
  FROM ruby:&lt;span class="nv"&gt;$RUBY_VERSION&lt;/span&gt;
  ARG &lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this block, we set the &lt;code&gt;RUBY_VERSION&lt;/code&gt; and &lt;code&gt;DEBIAN_FRONTEND&lt;/code&gt; build arguments and specify the docker image we will use as our base image.&lt;/p&gt;

&lt;p&gt;The first &lt;code&gt;ARG&lt;/code&gt; sets a default value for &lt;code&gt;RUBY_VERSION&lt;/code&gt;, but passing in a value from the command line or a &lt;code&gt;docker-compose&lt;/code&gt; file will override it, as you'll see later in the guide. An &lt;code&gt;ARG&lt;/code&gt; defined before &lt;code&gt;FROM&lt;/code&gt; is only available for use in &lt;code&gt;FROM&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FROM&lt;/code&gt; in a &lt;code&gt;Dockerfile&lt;/code&gt; is the base image for building our image and the start of the build stage. In our case, we are using the official Ruby image from Docker Hub, defaulting to Ruby 2.6.&lt;/p&gt;

&lt;p&gt;The next &lt;code&gt;ARG&lt;/code&gt; is used to set the build stage shell to &lt;code&gt;noninteractive&lt;/code&gt; mode, which is the general expectation during the build process. We don't want this environment variable to carry over to when we are using the images in our development environment, which is why we are using &lt;code&gt;ARG&lt;/code&gt; instead of &lt;code&gt;ENV&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Base Software Install
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  ARG &lt;span class="nv"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11
  RUN curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://deb.nodesource.com/setup_&lt;span class="nv"&gt;$NODE_VERSION&lt;/span&gt;.x | bash -
  RUN curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb https://dl.yarnpkg.com/debian/ stable main"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /etc/apt/sources.list.d/yarn.list

  RUN apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    build-essential &lt;span class="se"&gt;\&lt;/span&gt;
    nodejs &lt;span class="se"&gt;\&lt;/span&gt;
    yarn &lt;span class="se"&gt;\&lt;/span&gt;
    locales &lt;span class="se"&gt;\&lt;/span&gt;
    git &lt;span class="se"&gt;\&lt;/span&gt;
    netcat &lt;span class="se"&gt;\&lt;/span&gt;
    vim &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;sudo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here we are setting up the necessary software and tools for running a modern Rails app. Like in the previous section, we are setting a default for our &lt;code&gt;NODE_VERSION&lt;/code&gt; build argument.&lt;/p&gt;

&lt;p&gt;The next two &lt;code&gt;RUN&lt;/code&gt; lines set up the defined version of the node apt repository and the latest stable yarn apt repo. Since Webpacker started being officially supported in Rails 5.1, it is important we have a recent version of node and yarn available in the image we will be running Rails on.&lt;/p&gt;

&lt;p&gt;The third &lt;code&gt;RUN&lt;/code&gt; will look familiar if you have used any Debian based OS. It updates the apt repositories, which is important since we just installed two new ones, and then it installs our base software and tools.&lt;/p&gt;

&lt;p&gt;I want to point out &lt;code&gt;netcat&lt;/code&gt; and &lt;code&gt;sudo&lt;/code&gt; specifically. &lt;code&gt;netcat&lt;/code&gt; is a networking tool we will use to verify the other services are up when we are bringing up our Rails app through Docker Compose. We install &lt;code&gt;sudo&lt;/code&gt; since by default it is not installed on the Debian based Docker images, and we will be using a non-root user in our Docker image.&lt;/p&gt;
&lt;h3&gt;
  
  
  Non-root User
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  ARG UID
  ENV UID &lt;span class="nv"&gt;$UID&lt;/span&gt;
  ARG GID
  ENV GID &lt;span class="nv"&gt;$GID&lt;/span&gt;
  ARG &lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby
  ENV USER &lt;span class="nv"&gt;$USER&lt;/span&gt;

  RUN groupadd &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$GID&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      useradd &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$UID&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      usermod &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt; ALL=NOPASSWD: ALL"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/sudoers.d/50-&lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Docker containers generally use the root user, which is not inherently bad, but is problematic for file permissions in a development environment. Our solution for this is to create a non-root user and pass in our &lt;code&gt;UID&lt;/code&gt;, &lt;code&gt;GID&lt;/code&gt;, and username as build arguments.&lt;/p&gt;

&lt;p&gt;If we pass in our &lt;code&gt;UID&lt;/code&gt; and &lt;code&gt;GID&lt;/code&gt;, all files created or modified by the user in the container will share the same permissions as our user on the host machine.&lt;/p&gt;

&lt;p&gt;You will notice here that we use the build arguments to set the environment variable (via &lt;code&gt;ENV&lt;/code&gt;) since we will also want these variables available when we bring up the container.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;RUN&lt;/code&gt; instruction we add a standard Linux user/group and then we add the new user to the &lt;code&gt;sudoers&lt;/code&gt; file with no password. This gives us all the benefits of running as root while keeping file permissions correct.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ruby, RubyGems, and Bundler Defaults
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  ENV LANG C.UTF-8

  ENV BUNDLE_PATH /gems
  ENV BUNDLE_HOME /gems

  ARG &lt;span class="nv"&gt;BUNDLE_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20
  ENV BUNDLE_JOBS &lt;span class="nv"&gt;$BUNDLE_JOBS&lt;/span&gt;
  ARG &lt;span class="nv"&gt;BUNDLE_RETRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
  ENV BUNDLE_RETRY &lt;span class="nv"&gt;$BUNDLE_RETRY&lt;/span&gt;

  ENV GEM_HOME /gems
  ENV GEM_PATH /gems

  ENV PATH /gems/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Explicitly setting the &lt;code&gt;LANG&lt;/code&gt; environment variable specifies the fallback locale setting for the image. &lt;code&gt;UTF-8&lt;/code&gt; is a sane fallback and is what locale defaults to when it's working properly.&lt;/p&gt;

&lt;p&gt;We will be using a &lt;code&gt;volume&lt;/code&gt; with Compose, so we need to point RubyGems and Bundler to where that &lt;code&gt;volume&lt;/code&gt; will mount in the file system. We also set the gem executables in the path and set some defaults for Bundler, which are configurable via build arguments.&lt;/p&gt;
&lt;h3&gt;
  
  
  Optional Software Install
&lt;/h3&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;# Postgres Client:&lt;/span&gt;
  &lt;span class="c"&gt;#-----------------&lt;/span&gt;
  ARG &lt;span class="nv"&gt;INSTALL_PG_CLIENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false

  &lt;/span&gt;RUN &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTALL_PG_CLIENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; postgresql-client &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here is an example of how to set up optional software installs in the Dockerfile. In this scenario, &lt;code&gt;postgresql-client&lt;/code&gt; will not be installed by default, but will be installed if we pass a build argument set to true (&lt;code&gt;INSTALL_PG_CLIENT=true&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;
  
  
  Dockerfile Final Touches
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  RUN &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GEM_HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GEM_HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  RUN &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; /app

  WORKDIR /app

  RUN &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; node_modules &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; node_modules
  RUN &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; public/packs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; public/packs
  RUN &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; tmp/cache &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; tmp/cache

  USER &lt;span class="nv"&gt;$USER&lt;/span&gt;

  RUN gem &lt;span class="nb"&gt;install &lt;/span&gt;bundler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To wrap up the &lt;code&gt;Dockerfile&lt;/code&gt;, we create and set permissions on needed directories that were referenced previously in the file or that we will be setting up as volumes with Docker Compose.&lt;/p&gt;

&lt;p&gt;We call &lt;code&gt;USER $USER&lt;/code&gt; here at the end of the file, which will set the user for the image when you boot it as a container.&lt;/p&gt;

&lt;p&gt;The last &lt;code&gt;RUN&lt;/code&gt; command installs Bundler, which may not be required depending on the version of Ruby.&lt;/p&gt;

&lt;p&gt;Next, we'll take a look at our &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Docker Compose
&lt;/h2&gt;

&lt;p&gt;Docker's definition of Compose is a great place to start.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services. Then, with a single command, you create and start all the services from your configuration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is what we are going to be doing in this section of the guide, adding support for a multi-container Docker application.&lt;/p&gt;

&lt;p&gt;The first thing we will need is a &lt;code&gt;docker-compose.yml&lt;/code&gt; file at the root of the Rails app. If you would like to copy and paste the following YAML into a &lt;code&gt;docker-compose.yml&lt;/code&gt; file, I'll breakdown its parts below.&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;

  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./docker/ruby&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RUBY_VERSION=2.6&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_JOBS=15&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_RETRY=2&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_VERSION=12&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;INSTALL_PG_CLIENT=true&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UID=500&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GID=500&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_USER=postgres&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_HOST=postgres&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rails server -p 3000 -b '0.0.0.0'&lt;/span&gt;
      &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/ruby/entrypoint.sh&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app:cached&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gems:/gems&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;packs:/app/public/packs&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rails_cache:/app/tmp/cache&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
      &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:11&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_HOST_AUTH_METHOD=trust&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres:/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;gems&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;node_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;packs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rails_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, we are defining two services: the &lt;code&gt;rails&lt;/code&gt; service, which our Rails app will run in, and the &lt;code&gt;postgres&lt;/code&gt; service, which will accommodate PostgreSQL. The names for these services are arbitrary and could easily be &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt;, but since the service names are used for building, starting, stopping, and networking, I would recommend naming them close to the actual service they are running.&lt;/p&gt;

&lt;p&gt;We also are setting our Compose file compatibility to &lt;code&gt;version: '3.7'&lt;/code&gt; which is the latest at the time of this writing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The App Service Broken Down
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;

  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./docker/ruby&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RUBY_VERSION=2.6&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_JOBS=15&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_RETRY=2&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_VERSION=12&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;INSTALL_PG_CLIENT=true&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UID=500&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GID=500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first part of the &lt;code&gt;rails&lt;/code&gt; service is to set up to build the image from the &lt;code&gt;Dockerfile&lt;/code&gt; we created earlier. Once built, it uses the local image from that point forward. We set the &lt;code&gt;context&lt;/code&gt;, which is the directory path where our &lt;code&gt;Dockerfile&lt;/code&gt; is stored. The &lt;code&gt;args:&lt;/code&gt; key specifies the build arguments we set up in the &lt;code&gt;Dockerfile&lt;/code&gt; earlier in the guide. Notice that we are overriding some of our earlier defaults here.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;. . .&lt;/span&gt;
      &lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_USER=postgres&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_HOST=postgres&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rails server -p 3000 -b '0.0.0.0'&lt;/span&gt;
      &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/ruby/entrypoint.sh&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app:cached&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gems:/gems&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;packs:/app/public/packs&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rails_cache:/app/tmp/cache&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the next part, we set up &lt;code&gt;environment&lt;/code&gt; variables for connecting to the &lt;code&gt;postgres&lt;/code&gt; service, which will require updating your &lt;code&gt;database.yml&lt;/code&gt;&lt;a href="https://gist.github.com/nvick/c4e80964c7a7990b3bf4387de2f2b5b6"&gt;example&lt;/a&gt; file to take advantage of these variables.&lt;/p&gt;

&lt;p&gt;We also set the default &lt;code&gt;command&lt;/code&gt; that will run when &lt;code&gt;docker-compose up&lt;/code&gt; runs, which in this case starts the Rails server on port 3000 and binds it to all IP addresses.&lt;/p&gt;

&lt;p&gt;Next, we point to our &lt;code&gt;entrypoint&lt;/code&gt; script, which we'll revisit after breaking down the rest of the Compose file.&lt;/p&gt;

&lt;p&gt;After the &lt;code&gt;entrypoint&lt;/code&gt;, we set up &lt;code&gt;volumes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first volume mounts the current directory (&lt;code&gt;.&lt;/code&gt;) to &lt;code&gt;/app&lt;/code&gt; in the container. The mapping for this one is &lt;code&gt;HOST:CONTAINER&lt;/code&gt;. If you are on Mac, I would recommend using &lt;code&gt;.:/app:cached&lt;/code&gt; since file sharing on Docker for Mac is CPU bound. By setting &lt;code&gt;:cached&lt;/code&gt; on the mount point it allows files to be out of sync with the host being authoritative. &lt;a href="https://docs.docker.com/docker-for-mac/osxfs-caching/"&gt;Here are more details&lt;/a&gt; about file sharing performance on Docker for Mac. In our testing &lt;code&gt;:cached&lt;/code&gt; has been the best option for speed vs. tradeoffs.&lt;/p&gt;

&lt;p&gt;The next volumes are named volumes. Creating them happens when we bring up the containers for the first time and then they are persistent from up and down. These volumes are native to the Docker environment, so they operate at native speeds. The persistence and speed are why we have chosen to use them for &lt;code&gt;gem&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;packs&lt;/code&gt;, and &lt;code&gt;rails_cache&lt;/code&gt; storage; otherwise, we would have to reinstall both every time we bring our environment back up. Lastly, there is a top-level &lt;code&gt;volumes&lt;/code&gt; key at the very bottom of the &lt;code&gt;docker-compose.yml&lt;/code&gt; file, which is where they are defined.&lt;/p&gt;

&lt;p&gt;The last key in this section is &lt;code&gt;ports&lt;/code&gt;. Ports &lt;strong&gt;only&lt;/strong&gt; need to be defined to access the containers from the host otherwise container to container communication happens on the internal Docker network generally using service names.&lt;/p&gt;

&lt;p&gt;The mapping &lt;code&gt;"3000:3000"&lt;/code&gt; allows us to connect to the Rails server at &lt;code&gt;[localhost:3000](http://localhost:3000)&lt;/code&gt;. The mapping is &lt;code&gt;HOST:CONTAINER&lt;/code&gt; just like the volume mount, and it is recommended to pass them in as strings because YAML parses numbers in &lt;code&gt;xx:yy&lt;/code&gt; as base-60.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;. . .&lt;/span&gt;
      &lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
      &lt;span class="s"&gt;tty&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="s"&gt;stdin_open&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="s"&gt;depends_on&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This last section explicitly sets the &lt;code&gt;user&lt;/code&gt; to &lt;code&gt;ruby&lt;/code&gt;, which was set up in our &lt;code&gt;Dockerfile&lt;/code&gt; above.&lt;/p&gt;

&lt;p&gt;Use the next two keys: &lt;code&gt;tty&lt;/code&gt; and &lt;code&gt;stdin_open&lt;/code&gt;, for debugging with &lt;code&gt;binding.pry&lt;/code&gt; while hitting the Rails server. &lt;a href="https://gist.github.com/nvick/b4458670f9395de7d19a8e2d06894d9b"&gt;Check out this gist&lt;/a&gt; for more info.&lt;/p&gt;

&lt;p&gt;One thing to call out from that gist is to use &lt;strong&gt;ctrl-p + ctrl-q&lt;/strong&gt; to detach from the Docker container and leave it in a running state. The &lt;code&gt;depends_on&lt;/code&gt; key is used to declare other containers that are required to start with the service. Currently, it is only dependent on &lt;code&gt;postgres&lt;/code&gt;. No other health checks or validations are run; we will handle those in the &lt;code&gt;entrypoint&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Postgres Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;. . .&lt;/span&gt;
    &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:11&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_HOST_AUTH_METHOD=trust&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres:/var/lib/postgresql/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define our &lt;code&gt;postgres&lt;/code&gt; service next with pretty minimal configuration compared to what we went through for the &lt;code&gt;rails&lt;/code&gt; service.&lt;/p&gt;

&lt;p&gt;The first key, &lt;code&gt;image&lt;/code&gt;, will check locally first, then download the official postgres image from Docker Hub with a matching tag if needed. In this case, it will pull in &lt;code&gt;11.5&lt;/code&gt;. I chose 11 for this example because that is now the default on Heroku, but there are lots of image options available on Docker Hub.&lt;/p&gt;

&lt;p&gt;We are using a named volume here as well for our &lt;code&gt;postgres&lt;/code&gt; data.&lt;/p&gt;

&lt;p&gt;And, that wraps up the Compose file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter The Entrypoint
&lt;/h2&gt;

&lt;p&gt;There are a few requirements for starting a Rails server, e.g., the database running and accepting connections. We use the entrypoint, which is a bash script, to fulfill those requirements. Looking back at the compose file we should create this bash script at docker/ruby/entrypoint.sh and make it executable.&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;#! /bin/bash&lt;/span&gt;
  &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

  : &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_PATH&lt;/span&gt;:&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/app"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
  : &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_TEMP_PATH&lt;/span&gt;:&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP_PATH&lt;/span&gt;&lt;span class="s2"&gt;/tmp"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
  : &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_SETUP_LOCK&lt;/span&gt;:&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP_TEMP_PATH&lt;/span&gt;&lt;span class="s2"&gt;/setup.lock"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
  : &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_SETUP_WAIT&lt;/span&gt;:&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"5"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# 1: Define the functions to lock and unlock our app container's setup&lt;/span&gt;
  &lt;span class="c"&gt;# processes:&lt;/span&gt;
  &lt;span class="k"&gt;function &lt;/span&gt;lock_setup &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$APP_TEMP_PATH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="nv"&gt;$APP_SETUP_LOCK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;function &lt;/span&gt;unlock_setup &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$APP_SETUP_LOCK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;function &lt;/span&gt;wait_setup &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Waiting for app setup to finish..."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="nv"&gt;$APP_SETUP_WAIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# 2: 'Unlock' the setup process if the script exits prematurely:&lt;/span&gt;
  &lt;span class="nb"&gt;trap &lt;/span&gt;unlock_setup HUP INT QUIT KILL TERM EXIT

  &lt;span class="c"&gt;# 3: Wait for postgres to come up&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB is not ready, sleeping..."&lt;/span&gt;
  &lt;span class="k"&gt;until &lt;/span&gt;nc &lt;span class="nt"&gt;-vz&lt;/span&gt; postgres 5432 &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1
  &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB is ready, starting Rails."&lt;/span&gt;

  &lt;span class="c"&gt;# 4: Specify a default command, in case it wasn't issued:&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails server &lt;span class="nt"&gt;-p&lt;/span&gt; 3000 &lt;span class="nt"&gt;-b&lt;/span&gt; 0.0.0.0 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# 5: Run the checks only if the app code is executed:&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rails"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;
  &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Clean up any orphaned lock file&lt;/span&gt;
    unlock_setup
    &lt;span class="c"&gt;# 6: Wait until the setup 'lock' file no longer exists:&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$APP_SETUP_LOCK&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;wait_setup&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;

    &lt;span class="c"&gt;# 7: 'Lock' the setup process, to prevent a race condition when the&lt;/span&gt;
    &lt;span class="c"&gt;# project's app containers will try to install gems and set up the&lt;/span&gt;
    &lt;span class="c"&gt;# database concurrently:&lt;/span&gt;
    lock_setup
    &lt;span class="c"&gt;# 8: Check if dependencies need to be installed and install them&lt;/span&gt;
    bundle &lt;span class="nb"&gt;install

    &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;
    &lt;span class="c"&gt;# 9: Run migrations or set up the database if it doesn't exist&lt;/span&gt;
    &lt;span class="c"&gt;# Rails &amp;gt;= 6&lt;/span&gt;
    bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails db:prepare
    &lt;span class="c"&gt;# Rails &amp;lt; 6&lt;/span&gt;
    &lt;span class="c"&gt;# bundle exec rake db:migrate 2&amp;gt;/dev/null || bundle exec rake db:setup&lt;/span&gt;

    &lt;span class="c"&gt;# 10: 'Unlock' the setup process:&lt;/span&gt;
    unlock_setup

    &lt;span class="c"&gt;# 11: If the command to execute is 'rails server', then we must remove any&lt;/span&gt;
    &lt;span class="c"&gt;# pid file present. Suddenly killing and removing app containers might leave&lt;/span&gt;
    &lt;span class="c"&gt;# this file, and prevent rails from starting-up if present:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"server"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /app/tmp/pids/server.pid&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi
  fi&lt;/span&gt;
  &lt;span class="c"&gt;# 12: Replace the shell with the given command:&lt;/span&gt;
  &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will only call out a few things specific to working with Rails from this file. Below, we are using &lt;code&gt;nc&lt;/code&gt;(&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-netcat-to-establish-and-test-tcp-and-udp-connections-on-a-vps#how-to-use-netcat-for-port-scanning"&gt;netcat&lt;/a&gt;) to verify that &lt;code&gt;postgres&lt;/code&gt; is up before running any database commands. &lt;code&gt;nc&lt;/code&gt; doesn't just ping the server; it is checking that the service is responding on a specific port. This check runs every second until it boots. &lt;strong&gt;Note:&lt;/strong&gt; We are using the service name &lt;code&gt;postgres&lt;/code&gt; to connect to the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB is not ready, sleeping..."&lt;/span&gt;
  &lt;span class="k"&gt;until &lt;/span&gt;nc &lt;span class="nt"&gt;-vz&lt;/span&gt; postgres 5432 &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1
  &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB is ready, starting Rails."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we run our bundle and yarn install commands to make sure we have the latest dependencies. Once both of those have run, we will use the new Rails 6 &lt;code&gt;db:prepare&lt;/code&gt; method to either set up the database or run migrations. This can be handled in many different ways but my preference is to use Rails tools. (Note: the comment is how you would do it on Rails 5 or older)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  bundle &lt;span class="nb"&gt;install

  &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;
    &lt;span class="c"&gt;# Rails &amp;gt;= 6&lt;/span&gt;
    bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails db:prepare
    &lt;span class="c"&gt;# Rails &amp;lt; 6&lt;/span&gt;
    &lt;span class="c"&gt;# bundle exec rake db:migrate 2&amp;gt;/dev/null || bundle exec rake db:setup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, we check for any dangling PID files left from killing and removing the app containers. If the PID files are left our &lt;code&gt;rails server&lt;/code&gt; command will fail.&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"server"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /app/tmp/pids/server.pid&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start It Up
&lt;/h2&gt;

&lt;p&gt;With all of this in place, we run &lt;code&gt;docker-compose up&lt;/code&gt;, which will build and start our containers. It is also a great time to get coffee because building the app container will take some time. Once everything is built and up, point your browser at &lt;a href="http://localhost:3000"&gt;localhost:3000&lt;/a&gt; and your homepage, or if this is a new app, it will display the default Rails homepage. If you need to run specs or any other Rails command, running &lt;code&gt;docker-compose exec app bash&lt;/code&gt; in a separate terminal will launch a Bash session on the running app container.&lt;/p&gt;

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

&lt;p&gt;We now have a base-level Rails Docker development environment. &lt;a href="https://gist.github.com/nvick/96c22b18b6cb31fa458fafba32fa000f"&gt;Here is a gist&lt;/a&gt; of all the three files referenced in this guide. Now that you have an understanding of how to set up a Docker development environment, check out &lt;a href="https://github.com/hintmedia/railsdock"&gt;Railsdock&lt;/a&gt;! It is a CLI tool we are working on that will generate most of this config for you. PR's appreciated!&lt;/p&gt;

&lt;p&gt;In the next part of the series, we will set up more services and use Compose features to share config between similar services.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>docker</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Favorite Non-Technical Activities</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Sat, 02 Nov 2019 14:56:23 +0000</pubDate>
      <link>https://forem.com/natevick/favorite-non-technical-activities-2dhc</link>
      <guid>https://forem.com/natevick/favorite-non-technical-activities-2dhc</guid>
      <description>&lt;p&gt;What are your favorite non-technical activities?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>React Concurrency Mode Released(Experimentally)</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Sat, 02 Nov 2019 14:00:57 +0000</pubDate>
      <link>https://forem.com/natevick/react-concurrency-mode-released-experimentally-45of</link>
      <guid>https://forem.com/natevick/react-concurrency-mode-released-experimentally-45of</guid>
      <description>&lt;p&gt;Share your initial thoughts!&lt;/p&gt;

</description>
      <category>react</category>
      <category>discuss</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Rails 6 Released!!!</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Fri, 16 Aug 2019 20:26:34 +0000</pubDate>
      <link>https://forem.com/natevick/rails-6-released-3jmo</link>
      <guid>https://forem.com/natevick/rails-6-released-3jmo</guid>
      <description>&lt;p&gt;What are you looking forward to the most?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://weblog.rubyonrails.org/2019/8/15/Rails-6-0-final-release/"&gt;https://weblog.rubyonrails.org/2019/8/15/Rails-6-0-final-release/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/hint/rails-6-upgrade-best-practices-5934"&gt;https://dev.to/hint/rails-6-upgrade-best-practices-5934&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>discuss</category>
    </item>
    <item>
      <title>What are your favorite business tools that free you up to focus on development?</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Sun, 04 Aug 2019 15:49:50 +0000</pubDate>
      <link>https://forem.com/natevick/what-are-your-favorite-business-tools-that-free-you-up-to-focus-on-development-5d8j</link>
      <guid>https://forem.com/natevick/what-are-your-favorite-business-tools-that-free-you-up-to-focus-on-development-5d8j</guid>
      <description></description>
      <category>discuss</category>
    </item>
    <item>
      <title>I Updated Rails. Now Everything's on Fire.</title>
      <dc:creator>Nate Vick</dc:creator>
      <pubDate>Mon, 03 Jun 2019 20:32:48 +0000</pubDate>
      <link>https://forem.com/hint/i-updated-rails-now-everything-s-on-fire-245f</link>
      <guid>https://forem.com/hint/i-updated-rails-now-everything-s-on-fire-245f</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on&lt;/em&gt; &lt;a href="https://hint.io/blog/updated-rails-everything-is-on-fire"&gt;&lt;em&gt;Hint's blog&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Following up on my &lt;a href="https://hint.io/blog/rails-6-upgrade-best-practices"&gt;best practices post&lt;/a&gt;, I have a prime example of one common issue that surfaces when upgrading a Rails app.&lt;/p&gt;

&lt;p&gt;I decided to spike on upgrading a small internal app to Rails &lt;code&gt;6.0.0.rc1&lt;/code&gt;. The test suite is green on Rails &lt;code&gt;5.2.3&lt;/code&gt; and Ruby &lt;code&gt;2.6.2&lt;/code&gt;, so I bumped the Rails version in the Gemfile.&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;source&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;https://rubygems.org&amp;gt;'&lt;/span&gt;
&lt;span class="n"&gt;git_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:github&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;https://github.com/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git&amp;gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="s1"&gt;'2.6.2'&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'6.0.0.rc1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the terminal, I ran &lt;code&gt;bundle update rails&lt;/code&gt; and it updated without issue (I mentioned this is a small app, right?). Even on small apps, this should make you raise an eyebrow.&lt;/p&gt;

&lt;p&gt;Skeptically, I ran the test suite.&lt;/p&gt;

&lt;p&gt;Errors all the way down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;An error occurred &lt;span class="k"&gt;while &lt;/span&gt;loading ./spec/services/step_processor_spec.rb.
Failure/Error: require File.expand_path&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../../config/environment'&lt;/span&gt;, __FILE__&lt;span class="o"&gt;)&lt;/span&gt;

TypeError:
  Rails::SourceAnnotationExtractor is not a class/module
&lt;span class="c"&gt;# /gems/gems/haml-rails-1.0.0/lib/haml-rails.rb:49:in `block in &amp;lt;class:Railtie&amp;gt;'&lt;/span&gt;
&lt;span class="c"&gt;# ...more output...&lt;/span&gt;

Finished &lt;span class="k"&gt;in &lt;/span&gt;0.00027 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 1.41 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
0 examples, 0 failures, 17 errors occurred outside of examples
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the error, let's take a look at &lt;code&gt;haml-rails&lt;/code&gt; and &lt;code&gt;SourceAnnotationExtractor&lt;/code&gt; in Rails to see what changed and if there is a newer version of &lt;code&gt;haml-rails&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But wait. You may ask: "Why not try upgrading the gem and move on?". Taking the time to understand the root cause of a problem provides confidence in the solution and clear direction to resolve broader symptoms throughout the codebase.&lt;/p&gt;

&lt;p&gt;Taking the time to understand the root cause of a problem provides confidence in the solution and clear direction to resolve broader symptoms throughout the codebase.&lt;/p&gt;

&lt;p&gt;Here is line 49 in &lt;code&gt;lib/haml-rails.rb&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="no"&gt;SourceAnnotationExtractor&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Annotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_extensions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'haml'&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;tag&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sure enough, there is a change in Rails 6 that moves &lt;code&gt;SourceAnnotationExtractor&lt;/code&gt; to &lt;code&gt;Rails::SourceAnnotationExtractor&lt;/code&gt; (&lt;a href="https://github.com/rails/rails/pull/32065"&gt;PR #32065&lt;/a&gt;). The top-level class has been deprecated, but the deprecation warning does not apply to the sub-classes which raise the &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Knowing the source of the error, we can check if there is a newer version of &lt;code&gt;haml-rails&lt;/code&gt; compatible with Rails 6. Again, you will find value by not just upgrading the gem, but understanding the changes in the newer versions. With &lt;code&gt;haml-rails&lt;/code&gt;, there is a newer version that has a fix for the issue. By bumping up to that version, I'm able to run the test suite without errors and get back to the upgrade at hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://calendly.com/natevick/upgrade-intro-call"&gt;Schedule a quick call&lt;/a&gt; with me to see how our expertise can keep your team delivering features during your Rails upgrade.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
