Today we will show you how to do this with Docker and what are the first steps to “dockerize” a Ruby application.
What is Docker
Docker is a tool that allows developers to specify which is the operating system configuration that will run the application. Docker provides an abstraction layer for configurations of this virtual environment called the container. What makes docker to be adopted on a large scale is that it allows you to package all the dependencies of a stack into a single container unit.
Docker installation
See the official installation manuals for your operating system.
But basically you can run on Linux:
sudo apt-get install docker
Or on OSX:brew install docker
brew install docker
Multiple environments with Docker
In real life working with projects, you need to organize images to work with various environments. For example, in a production image, you do not need to have your text editor set up.
In an image that just runs your test suite, you do not need to have the same tunning configuration of the database.
Images
The images are for you to have a bootstrap of your application. So if you want to upload an image with Ubuntu you can create a Dockerfile with the following content:
FROM ubuntu CMD echo "I am container running Ubuntu"
Wow! With just one FROM <system-operating-image>, you can already start a machine with Ubuntu.
To dock an app, that is, port to the world of containers, we need to define which operating system and which tools we install. There are some official images that already guarantee the basic set of tools for each stack. So it’s worth extending these images and adapting to your need. In this example below, we’ll use the official image that contains the ruby.
FROM ruby RUN ruby -e "p ENV"
The images can also be versioned, and each version gets split with <image:version>, so it could be FROM ruby:2.2.4to specifically download the 2.2.4 version.
In Dockerfile you have specified the image you want to use and a command you want to execute within the image.
This way, we now need to download the image and build locally with docker build.
Docker build
To build the image above, you must use the docker building, the folder where the Dockerfile is.
When running locally, you will have an output like this:
docker build. Sending build context to Docker daemon 2.048 kB Step 1 : FROM ruby ---> cc0ac907dc6d Step 2 : RUN ruby -e "p ENV" ---> Running in e0dc789ac0b0 {"RUBY_MAJOR"=>"2.3", "BUNDLER_VERSION"=>"1.11.2", "HOSTNAME"=>"e5c68db50333", "RUBYGEMS_VERSION"=>"2.6.2", "HOME"=>"/root", "BUNDLE_APP_CONFIG"=>"/usr/local/bundle", "BUNDLE_BIN"=>"/usr/local/bundle/bin", "RUBY_VERSION"=>"2.3.0", "PATH"=>"/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "BUNDLE_PATH"=>"/usr/local/bundle", "GEM_HOME"=>"/usr/local/bundle", "RUBY_DOWNLOAD_SHA256"=>"ba5ba60e5f1aa21b4ef8e9bf35b9ddb57286cb546aac4b5a28c71f459467e507", "PWD"=>"/", "BUNDLE_SILENCE_ROOT_WARNING"=>"1"} ---> 2e5fdfd56049 Removing intermediate container e0dc789ac0b0 Successfully built 2e5fdfd56049
Now if you try to run again, you will see that the image in the output has already been cached.
➜ ruby docker build . Sending build context to Docker daemon 2.048 kB Step 1 : FROM ruby ---> 2e5fdfd56049 Step 2 : RUN ruby -e "p ENV" ---> Using cache ---> 549f215a2569 Successfully built 549f215a2569
Note that it has not rotated the Ruby command because this part is already cached.
But what happened in practice? Let’s inspect the images.
Docker images
Use the command docker images to check which images are available on your computer:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ruby latest 549f215a2569 5 minutes ago 725.4 MB
725 MB? To make a simple hello worldCry
The official ruby image is based on UBUNTU and so it’s pretty big.
There is a version based on Alpine Linux , which is a more compact version of linux.
So let’s change the Dockerfile to use the alpine.
FROM ruby:alpine RUN ruby -e "p ENV"
And we will update the image again by running one $ ruby docker build..
And now by checking the size of the image:
$ ruby docker images REPOSITORY TAG IMAGE ID CREATED SIZE ruby latest a3446813b648 About a minute ago 125.3 MB
Much better with 125.3 MB.
Cool, now we have a space to work with ruby inside a container. Just synchronize the project into the container. And there are some ways to do this:
Copying resources
Doing the build, copying the application files to the image is useful when you want to do the self-contained deploy of the application. Changes to these files will need to be committed to the container, which makes development more difficult, but to deploy in production environments is the best way to work.
Through volumes
Mapping volumes into containers are useful when you want to change the container’s files without having to rebuild the image. Ideal for development environments, where you change a file and have the need to test the changes in time.
Linking Features
Linking features are useful for making containers more compact. Ideal for organizing and keeping containers simple but sharing information with each other.
Docker run
The docker command buildgenerated an image of a container with Ruby installed.
This allows you to reuse this image. Extend the image by adding more functionality or configuring it to the project specifications.
You can also run a command directly from within the container.
docker run -it ruby:alpine bash -c "ruby -e '3.times {|i|puts i}'" 0 1 2
Nice! I’m running ruby without having to install anything locally!
Build Options
In the build process, it is possible to create a repository/ tag in the image with the option -t <repo/tag>. This can be useful for re-taking pictures in the future. Example:
docker build -t digitalresults/sinatra.
Checking the image with docker images:
REPOSITORY TAG IMAGE ID CREATED SIZE digitalresults/sinatra latest 1f305fc79a88 About a minute ago 141.2 MB
The image now appears with REPOSITORY and will make it easier to reuse in other containers.
To run the server with the image type:
docker run -p 4567:4567 digitalresults/sinatra ruby app.rb [2017-02-28 15:13:12] INFO WEBrick 1.3.1 [2017-02-28 15:13:12] INFO ruby 2.3.1 (2017-02-28) [x86_64-linux] == Sinatra (v1.4.7) has taken the stage on 4567 for development with backup from WEBrick [2017-02-28 15:13:12] INFO WEBrick::HTTPServer#start: pid=1 port=4567
Where it -pserves to bridge the gap between <door of container>:<local door>.
Trying to access locally via localhost: 4567 will not be available because the bind was made to 127.0.0.1 while the docker waits for the host 0.0.0.0.
That way we will have to change the server sinatra to bind in the correct host:
require "bundler/setup" require "sinatra" set :bind, '0.0.0.0' $counter = 0 get "/counter" do puts "counter: #{$counter += 1}" end
Registry
The docker registry is the official platform provided by the docker staff itself for you to keep the docker images and share images.
Docker push
With the docker push command, you can share your updated image in an image repository or in the Docker Hub. Afterward, it is not necessary to run docker build recompiling everything. So just make one docker pull to download the finished image.
Docker pull
The docker pull command is used to download the ready-made images from the repository.
Each image made up of multiple layers and you can download and synchronize only the fragments of the image.
Docker compose
You should now be thinking of installing your entire toolbox into a container and not messing up your machine by spreading random libraries wherever you go.
But slow! Take a look at the docker-compose that he serves exactly for this! It helps you compose container units and work with a subset of containers that talk to each other.
Already in the process of using in production, it is also worth seeing:
Kubernetes and Docker swarm for orchestration of containers
Apache mesos and [open shift] for PAAS
A little more!
And you’re using Docker in your day to day life? Be sure to share your experience in the comments!