Bootstrapping a development environment can be overwhelming. What starts out as a project that should take only a few minutes, quickly turns into hours of searching and reading Stack Overflow. It seems that Ruby is especially difficult to get a sane, up to date, environment running. Maybe my system libraries are different than yours, or my environment is slightly different, whatever the case may be, it takes far too long to get going than it should.
In this tutorial we are going to bootstrap a modern Ruby development environment on OS X and create a brand new Ruby on Rails application using only Docker. Using Docker guarantees that your development environments will be the same and best of all: reproducible!
docker-compose. Then we will bootstrap a Ruby project and then a Rails app entirely within Docker. After setting up Rails to work with a Postgres database we will deploy a simple scaffold to production using Tutum and bootstrap the database there.
Check out and deploy the completed tutorial on GitHub.
The first thing that we need is Docker. Docker currently only runs on Linux, so if you are running OS X you will need to run Linux inside a virtual machine, but don’t worry, there is a great tool that can do that with one download. Boot2docker is a single package that will install the following:
- Docker client
- Docker engine
Please install Docker now by following one of these instructions. Here are a couple tutorials on installing Docker:
And the official documentation:
NOTE: I’ve installed boot2docker many times and it seems to always work well. If you have any issues with the installation process please see their GitHub project.
The second and last dependency that we need is Docker Compose. This tool will make it really easy to launch multiple Docker containers that all work together. It will allow us to launch Rails and a database with one command.
Installation from these docs should be fairly simple:
curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose
Boot me up Docker
Let’s create our VM that we will be using to run Docker in.
boot2docker init boot2docker start
Follow the instructions to setup the Docker environment (copy and paste 3 lines of code into your terminal).
Once you are done you should be able to execute
docker ps and see this output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Basic Ruby setup
Now that our prerequisites have been installed, it’s time to get our Rails app bootstrapped.
Create a new directory on your machine that will house our Rails app and
cd into it:
mkdir myApp && cd myApp
Now let’s create our Docker environment by creating a new file called
Dockerfile, with the following content:
Finally, we need a
Gemfile that will setup our Ruby dependencies:
source 'https://rubygems.org' gem 'rails'
Bundle it like it’s Twenty Fifteen
Now comes the fun part. If you were developing this Rails app locally the next step would be to run
bundle install to download all of the dependent gems. A default install of OS X does include Ruby, but I want to assume that my local version of Ruby is not the same as your version, and that either one of our Rubies could be broken. So let’s use Docker to make sure we are both on the same page and that the environment is guaranteed to work.
Execute the following command to run
bundle install from the Docker environment:
docker run -it -v $(pwd):/usr/src/app -w /usr/src/app ruby:2.2 bundle install
What we are doing here is starting up a new container using the
ruby:2.2 Docker image. We are setting our local directory, which we find by executing a sub shell running
pwd, to attach to the container at
/app. This will mount the
Gemfile into the container, as well as allow the container to drop a
Gemfile.lock back to our code base. The
-w /app flag sets our working directory inside the container. And finally, we tell the container to execute
At this point we should now have three files in our
Fresh and clean Rails
Now that we have our code base setup with the basic Ruby stack, it’s time to get Rails up and running. We will be using this Rails container over and over, so let’s make it easy on ourselves and use Docker Compose to automate things.
docker-compose.yml file with this content:
web: build: . command: rails server -b 0.0.0.0 ports: - '3000:3000' volumes: - '.:/usr/src/app'
This will let us start and stop a
web container, and also let us execute commands within the context of that container. The
build section tells Compose that this container needs to be built from the
Dockerfile found at the current directory. The
command section defines what command is executed when the container starts. We want to run the
rails server command. Note that we need to use the
-b 0.0.0.0 flag to tell the server to bind to an IP address that we can access from our Mac. Our container has it’s own networking stack, by default when the rails server binds to
localhost we won’t be able to access it. The
ports section instructs Compose to mount our app at port
3000 both on the outside (the first argument) and inside the container. The
volumes sections tells Compose that we want to mount our local directory
/usr/src/app in the container.
NOTE: The directory
/usr/src/app is a convention used by the
ruby docker image.
Build the container image with:
And now we can initialize Rails:
docker-compose run web rails new .
It will ask if it can override your
Gemfile, go ahead and let it do so:
Overwrite /usr/src/app/Gemfile? (enter "h" for help) [Ynaqdh] y
At this point we need to make a couple small modifications to the default Rails
Gemfile. On or near line 15 we need to uncomment the following line:
# gem 'therubyracer', platforms: :ruby
At the end of the
Gemfile we need to add the
rails_12factor gem, so that Rails behaves well in a containerized environment:
Now that we have changed the
Gemfile we will need to rerun our Docker bundle command:
docker run -it -v $(pwd):/usr/src/app -w /usr/src/app ruby:2.2 bundle install && docker-compose build
You will need to rerun the above commands each time you change the
Gemfile. This is because the
ruby:2.2-onbuild image executes the
bundle install with the global config option
frozen enabled, which prevents it from installing any new gems. While it is inconvenient, this is actually a Good Thing. You wouldn’t want to build this project on a colleague’s dev machine, or a production build with gems other than what you specify in the
Serve it up
Now we should have a fully functioning, base Rails app! Let’s test it out by first getting the IP address of the Docker VM and noting it:
Then start up the rails server:
Open up a web browser and access the website at the IP shown above on port 3000. For example:
http://192.168.59.103:3000/. You should see the default rails page!
Take it up a notch (or two)
Now that we have a basic Rails app, it’s time to setup a database and really build something useful. Traditionally we would use the
sqlite adapter in our development environment, and then deploy to Postrgres or MySQL in production. Now that we have Docker however, we can really step this up and duplicate our production environment right here on our development machine. This ensures that what we see in dev will match up with what we see in production.
Let’s edit our
Gemfile again, and replace the following line (around line 7):
And rerun our build command:
docker run -it -v $(pwd):/usr/src/app -w /usr/src/app ruby:2.2 bundle install && docker-compose build
Now we need a database server as part of our development stack. Replace your
docker-compose.yml file with this:
db: image: postgres:9.4 environment: - POSTGRES_PASSWORD=asdfasdf web: build: . command: rails server -b 0.0.0.0 ports: - '3000:3000' volumes: - '.:/usr/src/app' links: - db
Here we have created a new container called
db and have setup an environment variable called
POSTGRES_PASSWORD that will set the default password. We made a change to the
web container that
links it to the
config/database.yml file to use the
default: &default adapter: postgresql timeout: 5000 database: myapp_development pool: 5 host: db port: 5432 username: postgres password: <%= ENV["DB_ENV_POSTGRES_PASSWORD"] %> development: <<: *default test: <<: *default database: myapp_test production: <<: *default database: myapp_production
Take notice of the
host setting above in the
default config section. The database container will always be available to the web container under the name
db. Even when we go to production, the database will still be available at the hostname
Another neat trick to notice here is the database
password configuration. We can access any of the
db container’s environment variables from the
web container, prefixed with
Let’s start the new stack:
Now we can create the database:
docker-compose run web rake db:create
You should be able to access the Rails default page without any errors.
Building an app with scaffolds
Phew! Now we have a basic Rails application setup to use a Postgres database and all of it runs on our local machine. Let’s create a scaffold with a basic model to get our app actually doing something.
docker-compose run web rails generate scaffold post title body:text published:boolean
Now run the migration:
docker-compose run web rake db:migrate
And access the new scaffold at
You can also run your tests like this:
docker-compose run web rake
Going to production
Now that we have a functioning Rails app that actually does something, let’s take it to production! I’ve found that deploying an app early and often helps to remove any potential gotchas before they get too big. Using Docker locally and deploying to Docker and Tutum in production really makes things easy, so this won’t be difficult.
First thing we need to do is login to the Tutum private repository, if you haven’t done so already:
docker login tutum.co
Now we will create a separate
Dockerfile for production use. This will only differ from our original
Dockerfile in a few ways. First it will run the Rails asset compiler, secondly it will setup the
RAILS_ENV environment variable to
production, and lastly it will setup a default command to run. Go ahead and create a new
Dockerfile.production with this content:
FROM ruby:2.2-onbuild RUN rake assets:precompile ENV RAILS_ENV production CMD ["rails", "server", "-b", "0.0.0.0"]
We might also want to create a
.dockerignore file. This will prevent any containers from picking up files in our filesystem that we don’t need to be in a container. Set it up with a single line:
Now we can build a production container that we can send to Tutum’s private registry:
docker build -t tutum.co/username/myapp -f Dockerfile.production .
username with your username. Then we can push it:
docker push tutum.co/username/myapp
db: image: postgres:9.4 environment: - POSTGRES_PASSWORD=something_Secret web: image: tutum.co/ianneubtest/myapp ports: - '80:3000' links: - db environment: - SECRET_KEY_BASE=my_really_really_really_really_really_really_really_really_long_string
You will notice that this is nearly identical to your
docker-compose.yml file. The only difference is that we’ve set an image for the
web container, and removed the
volumes section. Cool! Hit
Create & Deploy and we should be just about good to go.
After the Stack is in the
Running state, we can click on the
Endpoints tab to find the URL. Open it up in your browser, it will look something like this:
Now navigate to your new production service:
Uh oh! Rails is reporting an error. Back in the Tutum dashboard navigate to the stack, and then click on the
web service. Then click on the
You should see an error that says:
ActiveRecord::NoDatabaseError (FATAL: database "myapp_production" does not exist
Oh yeah! To bootstrap a new rails app in production we need to be able to execute
Tutum makes this really easy, and we can even do it right from the dashboard.
Click on the
Containers tab of the
web service. Now click on the blue button that looks like this
>_ next to the
This launches us into a terminal running inside our
Once it starts up simply execute the commands like normal:
rake db:create && rake db:migrate
Reload the page, and you should now see the Posts scaffold!
I hope this tutorial has helped you get setup with a local Docker-ized Ruby on Rails environment and helped you see how easy it is to deploy and work with your application in production on Tutum. This tutorial distills the basis of the real production work that I do with Ruby on Rails on a day to day basis. I’ve found it to be clean and very easy to reproduce across different developer’s machines, as well as CI/CD systems, and ultimately in production.
Let me know in the comments or on twitter if you have any questions!
The biggest drawback to this system, in my opinion, is all the extra typing required on the CLI. In a future post I will describe various ways that I’ve come up with to reduce the verbosity of the various commands above. Subscribe to our blog so you don’t miss it!
You can find the completed code base with an easy Deploy to Tutum button, on the GitHub repository.
It was pointed out to me by @ryanbigg on Twitter that using the default rails web server will perform poorly in production. Absolutely! I would not recommend using WEBrick in a full scale production website. This tutorial should help you get Ruby on Rails up and running with Docker, but it is only the start!