Developing tools to automate what you do often is common sense. But what if you want to share your tools with other developers? As soon as you have something that relies on more than a simple script, you’re going to be faced with dependencies and distribution headaches. Docker can solve that. “Anything you do more than twice should be automated” is a common mantra in the software industry. And it makes sense: doing something once, well that could be considered a one-off. Doing the same thing again? Coincidence. The third time you start the task, it’s time to bite the bullet and take the time to script it. The resulting script might be a few lines of simple But your task might be more complicated than Bash can handle. Or you might be refining a script which is becoming too complicated to code in simple Bash functions. You decide to write some nice object-oriented Python, or Ruby, or Java, or… some other language. You build the runnable scripts and you put them into Git so everyone can access them.
Soon, the complaints start rolling in: “I don’t have Python 3 – I have to stick with 2.7 because Fred’s widget-frobulator script requires it.” “I can’t install that Ruby gem requirement because I have something else that requires an earlier version.” “I’m not installing $ENVIRONMENT, I’ve got too much stuff installed already.” all my colleagues, all the time There must be an easier way to package tooling. Enter Docker.
Here’s an example: geocoder-in-a-box: takes a single argument and provides geographical information about it. The script is very simple, but it has two very important requirements: it needs Ruby 2.6.5, and it needs a specific version of Alex Reisner’s Geocoder library. We will additionally need to pass command-line arguments to it. The resulting image should go to a repository so the team can find it. I’ve put all the code into GitLab. For the sake of the example, I’m going to push to Docker Hub – but you (like us) probably have your own internal Docker repository too.
The bit that does the work: You can run this from the command line (it’s a runnable Ruby script), but you’ll probably need to
Next comes the You can build an image by running this, in the same directory as the The image will be built using the Ruby-2.6.5-alpine base (the The The Finally, the magic: If your tooling requires some special handling (environment variables, for instance, or specific actions pre- and post-run), you might want to After the build completes, it should dump out the following lines: Now we can try it out:
It’s working perfectly. We can create a nice alias for this, so that our colleagues don’t have to complain about long-winded Docker commands when they need to geolocate something. Aliases are typically added to developers’ shell startup scripts ( The ‘ Now we can just use the alias, as if it were a command installed locally. Docker never appears:
To push images to Docker Hub, you’ll need a username and password (go ahead and sort that out, I’ll get a cuppa ☕️) In your terminal session, log in to the hub using Docker uses the tag infrastructure to differentiate local images from Hub images. This is done by prepending your Hub username to the image:
Finally, push the image by referring to its tag: That’s it! Your colleagues can then use the full tag name in their aliases, and the image will be pulled from the Hub automatically:
Docker makes your life easier by providing a simple packaging methodology for your code. Your colleagues will love you (don’t they already?) because they gain access to awesome (well, you wrote it, so that’s a given) tooling without having to set up a complicated environment. If you want to know more about Docker, there are loads of tutorials online.. There’s a lot more you can do with it than what we’ve covered here.
bash
. Most developers have Bash installed, and it’s available on almost all platforms. So you’ve got a universal script that’ll run anywhere.
Geocoder-in-a-box
Code
#!/usr/bin/env ruby
require 'geocoder'
abort 'At least one argument must be supplied: try a location ("Stuttgart") or an IP address ("139.162.203.138")' unless ARGV.count > 0
result = Geocoder.search( ARGV[0] ).first
puts " #{ARGV[0]}: #{result.city}, #{result.state}. Lat/Long: #{result.coordinates}"
gem install geocoder -v 1.5.2
first. You can try it out:Docker Packaging
Dockerfile
. This tells Docker how to build an image containing the right version of Ruby, and get our dependency installed too. Here’s the code:# Docker Tools Demo - rgeo - geolocate something!
#
# Creates a docker image containing a small weather tool, to illustrate
# packing tools as ephemeral dockers.
#
# John Hawksley <john_hawksley@intergral.com>
FROM ruby:2.6.5-alpine
MAINTAINER John Hawksley <john_hawksley@intergral.com>
COPY ./rgeo.rb /rgeo.rb
RUN gem install geocoder -v 1.5.2
ENTRYPOINT ["/rgeo.rb"]
Dockerfile
:docker build -t rgeo .
FROM
directive). This is a compact version of Linux from Alpine Linux, into which Ruby 2.6.5 has been pre-installed.COPY
directive simply copies our script into the image. This doesn’t have to be a script – it could be its own distributable unit, like a Gem, Egg or Jar. The material being copied must be at the same folder level as Dockerfile or below, and there can be more than one COPY
.RUN
directive installs our dependency – Geocoder 1.5.2 – into the image. Again, multiple RUN
directives can appear. It’s not uncommon to see apk
or apt
package management commands appear here. The main purpose of these commands is build an environment with the right supporting packages and tooling, so your own code can run.ENTRYPOINT
. This tells Docker what to actually run, when we run the image. We copied the script into /rgeo.rb
(the COPY
directive above), and it’s runnable, so we can just run it.COPY
in a shell script which does the actually call of your tooling for you.Successfully built 77f1cb7cdf08
Successfully tagged rgeo:latest
.bashrc
, .zshrc
for example):alias geo='docker run --rm rgeo:latest'
--rm
‘ option tells Docker not to keep an image of the finished container. This makes the run ephemeral: nothing remains afterwards.Distribution via Docker Hub
docker login
. If everything goes well, you’ll see Login Succeeded
.docker tag rgeo:latest jhawksleyintergral/rgeo:latest
docker push jhawksleyintergral/rgeo:latest
Subsequent calls don't pull the image again - naturally. They use the cached version:
Conclusion