Ruby + Docker: introducing dockerapi gem.
Tags: Docker, Dockerapi, Gem, Ruby on Rails, Ruby

Ruby + Docker: introducing dockerapi gem.

In this article we will explore the functionalities of dockerapi gem, a Ruby gem to interact directly with Docker API through HTTP. Differently from other gems, this one uses HTTP requests to create a true API-like implementation behaviour. Other gems offer different approaches: if you want to interact with Docker in an object-oriented approach, the traditional docker-api would be a better fit. If you want to manipulate the Docker client in the host machine, see docker gem.

Why another gem?

Most gems seem to be abandoned for years, even the most famous ones. This alone wouldn’t be a great deal, but they also lack functionality: not only Containers and Images are important, but Networks, Volumes, Swarms, Nodes, Secrets and so on. This gem aims to implement as most of the Docker API functionalities as possible.

Gem usage

We are going to create a simple application to demonstrate the gem functionality, starting with the basics (Images and Containers) to understand the general usage and then expand it to other functionalities. Creating images and containers in a Ruby script or in IRB would be as simple as running a few commands. So, to spice up the learning process a little bit, we are going to create a Rails application and integrate our gem in this interface.

To see the gem in use without using Rails or to find examples of more features, this GitHub reposiroty contains plenty of examples.

Preview

At the end of this tutorial, our application will be able to perform the following:

Pull a Nginx image from DockerHub:

Create a new container based on this image:

Start the container using the interface.

Access the service on port 80.

Pre-requisites

Before starting you need set:

Docker (installation guide)

Add docker group to user user groups as needed

Ruby + Rails (quick and dirty setup here)

Don’t forget Rails dependencies like node, yarn, etc.

We are not covering the above, there are great tutorials on how to install and setup those. All the code created for this article is available here.

Rails app

Without further ado, let’s create a Rails application:

$ rails new dockerapi-demo -O -B $ cd dockerapi-demo

We are creating an application without active record (-O) and we are skipping bundle install (-B) for now.

Add dockerapi gem to the Gemfile

Install gems:

$ bundle install

Initial setup

Let’s create two controllers (Image and Container) and their views:

$ rails g controller image index new create delete $ rails g controller container index new create start stop delete

We’ll create a CRUD for each, but instead of reading and writing to a database, we’ll communicate with Docker API the retrieve and send information.

The above step created some lines in config/routes.rb, replace everything with:

Add the following in app/views/layout/application.html.erb, replacing the content of the file:

Docker Images

At this point, we can run the application, but we still don’t have any functionality. Let’s fix that by creating a form for pulling images. Edit app/views/image/new.html.erb:

Edit app/controllers/image_controller.rb creating the before_action hook and the private methods below:

The combination of set_image method and the before_action hook will create a new instance of the Dokcer API connection before executing other methods, pointing to endpoints related to Image commands.

For this exercise, we are only using repo (repository name) and tag to pull an Image, but many more options are available and described in the Gem documentation.

Now, let’s edit image#create in the same file:

When you access Pull image on the interface and submit the form, image#create is called. In the console log we have the confirmation that the Image was successfully pulled:

=> “{\”status\”:\”Status: Image is up to date for busybox:1.32.0-uclibc\”}\r

”

Let’s explore the code we created.

This line is using the connection that we created in the before_action hook. It’s using “fromImage” as hash key as request body parameter as described in the documentation (Docker API docs) for pulling an image from a registry:

fromImage: Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed.

Different options are used for different behaviour for the same endpoint. The API documentation is a good source to learn what the expected behaviour might be for each one.

This code will redirect the user to the image#index page in case of a successful request (status code 2xx), or show the form again otherwise.

Now, once we submit the form we’ll pull the image in case it doesn’t exist. You can give it a try! Notice that our index page won’t show anything even though we already have the Image in our filesystem so let’s work on that next!

Replace the content in app/views/image/index.html.erb with:

Change image#index to the following:

And image#destroy:

The index page is just iterating over the image list, showing repository name, image size in bytes and an option to delete the image.

This line queries the Docker API for a list of containers. The response has many elements, such as status, headers and more. We are interested in the JSON content inside the response body and this is exactly what Docker::API::Response#json method access.

This line tells the API to remove an Image with a given id, passed as request parameter. This parameter can be the Image name or Id, as specified in Docker API docs.

The complete ImageController class, with everything we created so far:

Docker Containers

Now that we have a simple CRUD for Images, we’ll do the same for Containers. In the form we’ll add host configuration to access a Nginx container in the browser. We’ve already created some files, let’s modify them accordingly.

Edit app/views/container/new.html.erb:

We need Image information for the container form, add the following to container#new in app/controllers/container_controller.rb:

We’ve seen some of these before, but let’s take a look.

We create a new connection to the API, pointing to Image related endpoints.

Docker::API::Image#list will list all available images.

Having the API response, we map one RepoTag for each Image and make them available in “@images” variable to the front end.

This is everything needed for the form to work, but we still need to set up a few things in the Container controller. Similarly to what we did before, let’s create the before_action hook to connect to the API and the form parameters:

And at last, let’s create container#create:

Let’s understand this code.

Since the information travels through the internet as text, we need to evaluate it to a Hash object, which is the expected object to be used as parameters.

This line will send a HTTP request to POST /container/create in the Docker API. The first hash is for query parameters and they usually are written in lower case. The second hash is for the request body and they are usually in upper case. It’s set like this because this is how it’s specified in Docker API documentation. For this particular endpoint, see this link and find more options for container creation.

Based on the response, we redirect the user to the Container list or back to the form.

Now you can try creating a Nginx container. Pull a Nginx image (I suggest using alpine tag), then in the container creation form, select the image and enter the following host configuration:

{PortBindings: {“80/tcp”: [ {HostIp: “0.0.0.0”, HostPort: “80”} ]}}

Complete example below:

Using the interface

The host config will link container port 80 to port 80 on the host.

NOTE: you can see that the host configuration is a string representation of a Ruby hash. It encompasses several nested elements, according to the Docker API documentation. In the example above, we have the following structure:

HostConfig > PortBindings > <Additional Properties> > HostIp + HostPort. Note it's the same nesting setup of the Docker API. For a better understanding, try comparing what we have above with the documentation and finding all the options we are using there as well.

If everything worked right, you went back to a blank index page. Let’s display useful information here. Replace the content in app/views/container/index.html.erb with:

In container#index add the following:

For the Docker::API::Container#list method, the only difference from what we did before with Images is the all: true option. Since only active containers are shown by default, we need to specify this flag to see inactive ones.

This line is adjusting the container name, removing the leading “/” character.

The Docker API creates the container, but it’s not started automatically. That’s why our interface has links to start and stop the containers.

Now that we are able to see our containers, let’s start our newer one. Modify container#start and container#stop as following:

At this point you may be familiar with the gem’s syntax: the start and stop methods within Docker::API::Container use the container id to tell the API which container to start or stop.

Now, let’s go ahead and hit the Start link for our Nginx container. The state should change to running and a Nginx welcome page should be available in the browser accessing the machine IP address (or localhost) on port 80 (assuming you are not using this port to anything else). Likewise, using the Stop link will stop the container, changing the status to exited and it will no longer be available in the browser.

To finish the Container CRUD, let’s create the deletion functionality. Edit container#destroy:

The complete ContainerController class:

We’ve come a long way, don’t we? Congratulations, you’re now manipulating Docker API using dockerapi gem!

What else?

We have so much more in the dockerapi gem. We can manage Volumes, Networks, Secrets, Swarms, Nodes, Services, and more! Try creating the Network management system and include a Network option in the Container creation form. Try the same for Volumes as well. If you go far enough, you can end up with your own version of Portainer! Take a look at the gem docs and see what other information come with the response object of each request. This would cover the basics of the gem usage and its capabilities. I hope you like it!

Conclusion

Creating Docker Images and Containers are the basics, dockerapi has much more. You don’t need to use it only within a Rails application, it’ll work in any Ruby workflow. Don’t have a local Docker installation? It works with remote API connection as well. Take a look at the GitHub repository and find out more!