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 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
ruby on rails backend.
mysql database
react-app
Provide helpful resources geared toward beginners.
I’ll show you how to install docker on mac.
Docker is also available for windows.
Prerequisites:
- basic commandline.
- a text editor. I will be using VS code.
-
attention to detail.
helpful but not nessesscary.
- knowledge of ruby
- knowlege of .yaml or .yml files
- Knowledge of docker.
helpful links.
Visit the official website and install docker.
Getting started
Open your terminal and verify docker installed correctly.
docker --version
Your output should look something like this but may be a different version.
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 and try do the steps again.
Before moving on, make sure that you open docker desktop.
press command + spacebar to open the searchable view.
then search for docker.
a small whale icon with shipping containers on its back should be visible at the top-right of your desktop.
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.
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.
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.
mkdir rails-docker
cd into the directory.
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.
touch Gemfile
Inside the Gemfile, we will write the source that we get our ruby gems.
source 'https://rubygems.org'
underneath the source add a line for the rails version we want.
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
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 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 command.
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
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
build-essential is a package of trusted compilers for our application.
libpq-dev is a compiler system to help with port forwarding to databases from virtual machines. A detailed explanation is in the link.
nodejs 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.
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 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.
WORKDIR /app
we have to ADD the Gemfile and the Gemfile.lock for docker to reference for the build of our rails app.
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.
RUN bundle install
now we want to ADD the current directory to the application directory after that is installed like so.
ADD . /app
the finished file should look like this.
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.
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.
version: '2'
services:
under services, we will define the external services for the app such as the database (MYSQL).
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.
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.
ports:
- "3307:3306"
let's add our next service called app:
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: .
build: .
now we will run rails with the command: then bind it to port 0.0.0.0
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.
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.
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.
depends_on:
- db
links:
- db
Now we want to add the environment variables for the rails application.
environment:
DB_USER: person
DB_NAME: app
DB_PASSWORD: password
DB_HOST: db
The whole file should look like this.
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.
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.
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
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.
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
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.
docker-compose up
When you go to localhost:3001 you will see the rails logo.
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.
Click on the bug icon in the top left corner of the screen.
Click the red clean/ purge data and yes, we are sure.
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.
right-click over the selected files and select delete and confirm.
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.
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.
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.
docker-compose build
While that's reinstalling, go into the database.yml and replace it with this file referencing the environment variables.
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...
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 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.
Check out the following.
docker-compose.yml
Dockerfile
and the database.yml in the config folder
an excellent youtube from
Chandra Shettigar who does an excellent tutorial 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.