Setting up Ruby on Rails with docker and MySQL
Tags: docker, ruby, rails, mysql
Published: 2020-06-25 18:02:37

Docker is a technology that will revolutionize the way that you develop.

If you like what I'm teaching, please give it a Heart and a friendly comment if you found this helpful. 

What I'm teaching in this tutorial is how to develop a rails application inside a docker container so that you only have to set up docker on your __mac__ to develop a rails application. (no rails or Ruby operating system  config required to start.) 

This is popular with businesses because [containerization](https://www.docker.com/resources/what-container) or, standardizing units of software, is a revolution in how applications are made.

if you are new to docker, and you follow this tutorial. This __will be__ the start to an amazing journey to get applications running that you can scale _indefinately_. You can start developing everything through a docker container without the stress of host system configurations. 

Here is what I'm going to teach. 

This is the start to a multi-part series that will teach you how to make 3 docker containers and publish them on heroku 

1. ruby on rails backend.

2. mysql database

3. react-app

Provide helpful resources geared toward beginners.
I’ll show you how to install docker on mac.

Docker is also available for [windows](https://docs.docker.com/docker-for-windows/install/).

__Prerequisites:__ 

   1. basic commandline. 
   2. a text editor. I will be using [VS code](https://code.visualstudio.com/download). 
   3. attention to detail.  
   
      helpful but not nessesscary.
    1. knowledge of ruby
    2. knowlege of .yaml or .yml files 
    3. Knowledge of docker.
   
   helpful links. 

   [docker docs](https://docs.docker.com/get-started/)

  Visit the official website and install docker. 

   [docker for mac desktop](https://docs.docker.com/docker-for-mac/install/)

   __Getting started__

  Open your terminal and verify docker installed correctly. 

```bash 
docker --version
```
Your output should look something like this but may be a different version.

```bash
Docker version 19.03.8, build afacb8b
```

if you didn't see any version or you got an error. Go back to the link where [docker for mac desktop](https://docs.docker.com/docker-for-mac/install/) and try do the steps again. 

Before moving on, make sure that you open docker desktop.

press command + spacebar to open the searchable view. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1fx0hlszz1wbnghq2bqk.png)

then search for docker. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gzioydgh8g0jilpxnhd7.png)

a small whale icon with shipping containers on its back should be visible at the top-right of your desktop. 


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/o637u2hpqifuqfgs2pqy.png)

Click on it. 

Now click to open desktop.

Make sure that docker desktop is running the whole time, if docker is off then docker commands will not execute. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/drj6mqzv0e2yamyex75b.png)

You will be prompted to do the hello world version of docker which I highly recommend doing the simple walk through.

If everything worked with the hello world app, one last check to ensure that you can follow this tutorial for a rails application is to ensure that you have access to docker-compose, this should be installed with the rest of docker command-line tools and this is the command to make sure.

```bash 
docker-compose -v 
``` 
_the expected output._

```
docker-compose version 1.25.5, build some-hexadecimal
```

If the versions aren't the same everything should still work. 

__Start of the tutorial.__

First, let's make an empty directory called rails-docker. 

```bash
mkdir rails-docker
```

cd into the directory.

```bash
cd rails-docker
```

The first file we want to make is the Gemfile, this is because we will have docker instantiate the entire dev environment instead of running rails commands directly through our host operating system. 

```bash
touch Gemfile
```

Inside the Gemfile, we will write the source that we get our ruby gems. 

```ruby
source 'https://rubygems.org'
```
underneath the source add a line for the rails version we want.

```ruby
gem 'rails', '5.0.7'
```

If you want the newest version of rails(_6.0.3.2 as of this writing_), you can install whichever version you want but this is the one that I have tested and recommend for this tutorial. 

We also need and empty Gemfile.lock, this is where the Gemfile versions are recorded by rails.  

__The app will not run without an empty Gemfile.lock__

```bash
touch Gemfile.lock
```

Now we need to make a Dockerfile, this is where we define what technologies our docker image will have, such as ruby versions node.js versions. 

```touch 
touch Dockerfile
```

Inside the Dockerfile we will specify the minimum requirements for docker to make an image to run a rails app. Each new command can be thought of like a script making layers that become an operating system.

First is the programming language that everything else will need to run on top of. In our case ruby, using the [FROM](https://docs.docker.com/engine/reference/builder/#from) command. 

```docker
FROM ruby:2.5.1 
```
_Dockerfile_

Next is the RUN command a typical suffix for RUN is apt-get update && apt-get install problems can arise because updating should happen outside your docker image on dockerhub. see a detailed explanation [here](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)

```docker
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
```

[build-essential](https://hub.docker.com/r/continuul/build-essential/) is a package of trusted compilers for our application.

[libpq-dev](https://zoomadmin.com/HowToInstall/UbuntuPackage/libpq-dev) is a compiler system to help with port forwarding to databases from virtual machines. A detailed explanation is in the link.

[nodejs](https://hub.docker.com/_/node/) is a javascript engine that will enable you to run javascript outside of a browser. 

We will add more RUN commands which you can think of as the docker equivalent of a command-line script. 

```docker
RUN mkdir /app
```

This will make a directory only within the context of our docker-image. 

Now we want to make the /app directory the working directory with the [WORKDIR](https://docs.docker.com/engine/reference/builder/#workdir) command, its the bash equivalent of cd into a folder. This means that all the commands that you run after this line will happen in the /app directory. 

```docker
WORKDIR /app
```
we have to ADD the Gemfile and the Gemfile.lock for docker to reference for the build of our rails app.

```docker
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
```

Since we have our Gemfile and the Gemfile.lock inside the directory where we will build, the next command is to RUN bundle install. This adds all of the rails dependencies.

```docker
RUN bundle install
```

now we want to ADD the current directory to the application directory after that is installed like so. 

```docker
ADD . /app
```

the finished file should look like this. 

```docker
FROM ruby:2.5.1 
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /app
WORKDIR /app 
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install
ADD . /app
```

this is the file that will be referenced every time we use the build command on the command-line. 

as the next step in our configuration, let’s make a docker-compose.yml. 

```bash
touch docker-compose.yml
```

In this file we will define a config object for the services involved in this app such as the database.  

Within this file, because of how docker objects are defined, improper indentation can cause the build to fail. 

I will provide a full list of the file after I walk you through what is going on with proper indentation.

```yaml
version: '2'
services: 
```
under services, we will define the external services for the app such as the database (MYSQL). 

```yaml
db:
    image: mysql:5.7
```

This is how we can pull an image database from docker hub, in our case MySQL. 

some configuration for how we want our database to behave on restart and access defined by environment: variables.
 
```yaml
restart: always
    environment:
        MYSQL_ROOT_PASSWORD: password
        MYSQL_DATABASE: app
        MYSQL_USER: user
        MYSQL_PASSWORD: password
```

We want to give our app a port forwarding service so that the process that docker is running is linked to the process the host operating system is running.

```yaml
    ports:
       - "3307:3306"
```

let's add our next service called app:

```yaml
app: 
```

below app, we want our app to reference the rails application. this means that instead of calling on an image from docker hub like we did with MySQL. We are going to tell docker to build the app that we configured in the Dockerfile with the build: 

we will add the current directory with build: .  

```yaml
  build: .
```
now we will run rails with the command: then bind it to port 0.0.0.0

```yaml
command: rails s -p 3000 -b '0.0.0.0'
```

we need a thing called volumes: 

It is used to say, the directory where the host system shares the /app directory. 

```yaml
        volumes: 
            - ".:/app"
```

we want to set up the port forwarding so that when docker refers its port 3000 in the container, it will forward that to 3001 to the operating system in the same way that we did so for the database. 

```yaml
    ports: 
       - "3001:3000"
```

let's make the app service depend_on the db: service so that when we use our rails app it refers to the database to store data and it is linked to the database.

```yaml
depends_on:
      - db
links:
      - db
``` 

Now we want to add the environment variables for the rails application.

```yaml
         environment:
             DB_USER: person
             DB_NAME: app
             DB_PASSWORD: password
             DB_HOST: db
```

The whole file should look like this. 

```yaml
version: '2'
services:
    db: 
        image: mysql:5.7
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: password
            MYSQL_DATABASE: app
            MYSQL_USER: user
            MYSQL_PASSWORD: password
        ports:
            - "3307:3306"
    app: 
        build: .
        command: bundle exec rails s -p 3000 -b '0.0.0.0'
        volumes: 
            - ".:/app"
        ports: 
            - "3001:3000"
        depends_on:
            - db
        links:
            - db
         environment:
            DB_USER: root
            DB_NAME: app
            DB_PASSWORD: password
            DB_HOST: db

```

_docker-compose.yml_


We are going to be interacting with the command-line again in a way that we prefix all of our ordinary rails commands with _docker-compose run app_ 

This is the command to download all of the boiler-plate rails.

__note: this will take a few minutes the first time but will be quick next time. the --api flag can be added, is optional and but it will be easier to get started if you use embedded ruby views instead of a front end framework.__

```bash
docker-compose run app rails new . --force --database=mysql --skip-bundle
```
This should have created a rails structure and a database structure to use but we still have some work to do. 

Open the database.yml in the config folder that was downloaded in the rails install. Delete all the comments.  

Without all the comments. It should look something like this. 

```yaml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password:
  host: localhost

#...rest of file... 
```

under pool: 5, erase the username, password, host and replace them with this code so that they reference the ENV variables in the docker-compose.yml 

```yaml
database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  host: <%= ENV['DB_HOST'] %>
```

We want to standardize the naming conventions by setting the rest of the file's dev, test, and production environments to default. 

```yaml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  host: <%= ENV['DB_HOST'] %>

development:
  <<: *default

test:
  <<: *default

production:
  <<: *default


```

We want to make the database username, password, and host reference environment variables that we created in the docker-compose.yml


Now, in the command-line we can run _docker-compose build_

```bash
docker-compose build
```

This will bundle install all the gems for the rails application that were pulled in from rails boiler-plate.

Now run _docker-compose up_ on the command-line in order to start both the MySQL database and the rails application. 

```bash
docker-compose up
```

When you go to [localhost:3001](HTTP://localhost:3001) you will see the rails logo. 


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/j7gsd5l89ts639yi691j.png)

__Trouble Shooting and Reconfiguration.__

I will be following this up with a multi-part series that shows how to deploy multiple containers to Heroku to make full-stack containerized applications. 

In order to do that easily, we have to know how to tear this app down and redo it, maybe you want to find out whether this will work with the latest version of ruby and rails. Or with a PostgreSQL database instead. 


The first step is to open docker dashboard and purge data. 


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2lsn1kl0wfidhd0uvey4.png)

Click on the bug icon in the top left corner of the screen.


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/g9zk1kkhb6zvyalz9m44.png)

Click the red clean/ purge data and yes, we are sure.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ri584e4pblfgc8vgmyzb.png)


Once you confirm this will restart docker, not opening the dashboard but running in the background so that you can use the command-line. 

now delete the rails app from the file directory, carefully so that you don't delete any of the docker related files, Gemfile or the Gemfile.lock

You can do this in vscode by clicking on the files and folders while holding down the command key.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zopz4rnby4fw6q3ovear.png)

right-click over the selected files and select delete and confirm. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cdqyid4zeyc8r154mntg.png)


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6q3ckcr9a0dcvplcu47x.png)
 

Open the Gemfile and delete everything except the source and the rails gem, you could delete the content of the Gemfile and copy and paste these two lines of code into the file. 

You could also try the rest of this rebuild with a different rails version and if it doesn't work out, you can repeat the steps above for troubleshooting and reconfiguration. 

```ruby
source 'https://rubygems.org'
gem 'rails', '~> 5.0.7', '>= 5.0.7.2'
```

Now we want to make the Gemfile.lock empty since it will get filled again when we run docker-compose build. 

This can easily be done by clicking on the file content and pressing command + a, highlighting everything then press delete, leaving the file empty. 

Now you can go to the command-line and run the steps for creating a new image._make sure docker is running first_ 

These commands are.

```bash
docker-compose run app rails new . --force --database=mysql --skip-bundle
```

This will take a while since it's just like the first time. 

Build what you pulled in. 

```bash
docker-compose build
```

While that's reinstalling, go into the database.yml and replace it with this file referencing the environment variables. 

```yaml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  host: <%= ENV['DB_HOST'] %>

development:
  <<: *default


test:
  <<: *default


production:
  <<: *default
```

To make sure it works run...

```bash
docker-compose up
```

if this doesn't work the first time you may have to restart docker which can be done by clicking the icon at the top right of the desktop and quitting the application. 

Visit [localhost:3001](HTTP://localhost:3001) and you will see yourself right back where you started. 

__Conclusion__

Docker has a learning curve but it will save you so much time in the long run not having to worry about operating system related errors. 

I will be doing more tutorials that branch off from this one showing you how to make full-stack web apps to production with rails and react. 

If you run into problems getting this to work, please leave a comment and I will get back to you. 

Here are some additional resources. 

finished tutorial on [github](https://github.com/TallanGroberg/docker-rails-tutoial). 

Check out the following. 

docker-compose.yml

Dockerfile

and the database.yml in the config folder

an excellent youtube from 
[Chandra Shettigar](https://www.youtube.com/channel/UCeMoPFyEAoQz398VAWx4BZg) who does an excellent [tutorial](https://www.youtube.com/watch?v=a-jcTib9ZPA&t=602s) on how to set up a ruby on rails app with docker but leaves the setup of docker to you and doesn't show you how to tear-down,
rebuild or reconfigure. 

If this helped you get started with docker, please give it a heart, unicorn and a tag. 

For the next part of this series where I will show you how to set up a react application within a docker container. 

Thanks and I look forward to hearing from you in the comments 
below.