A sensible semantic versioning process for Docker images
Tags: devops, docker, ruby, yaml
Published: 2019-10-07 22:22:30

I'm a big fan of Ruby.  I've literally used it almost every day since 2008 when I picked up the "hummingbird" book (v1.8.7).  Naturally, I am likely to have a Rakefile at the root of all my repositories.  GNU Make has its place, but Rake is always the clear winner in my book.

I've noticed that CI/CD pipelines for Docker images can be challenging for newcomers, so I wanted to share a best practice for bumping versions in a codified manner.  And no, I don't mean the goto `VERSION` text file.  This method works of course, but if you want something more sophisticated read on.

To get started, you'll need Ruby installed and create the following two files:

- `.version.yml`
- `Rakefile`

In our `.version.yml` file, let's add some content

```yaml
---
version: 0.0.1
promote: major
phase:         # optional

```
Our intention here is to cook up an initial release version `1.0.0`.  The value we're passing for the version doesn't really matter, as long as its below major version `1`.  The `promote:` key is going to determine the semantic bump that we plan to use.  Now let's create our Rakefile:

```ruby
require 'semantic'
require 'yaml'

def next_version
  # load the current version
  version_yml = YAML.safe_load(File.read('.version.yml'))
  current     = version_yml['version']
  level       = version_yml['level'].to_sym
  phase       = version_yml['phase']

  # create a forecast of what our future version will be
  version = Semantic::Version.new current
  version = version.increment!(level)
  "#{version}.#{phase}"
end

task :bump do
  sh "docker tag myproject:#{next_version}" # => myproject:1.0.0
end

```

So what exactly is happening here?  We've defined a method in the `#Kernel` namespace that we can employ throughout our Rake tasks.  It reads the YAML file and provides the appropriate bump to our version.  The `:bump` task simple runs the `docker tag` command, but could be used in conjunction with any other release mechanisms.

### Putting it to Work
In order for this to be useful to you and your team, you will need to discuss your release strategy.  Ideally, you should only bump your semantic versions on merges to master.  Depending on your CI/CD process, you would only run the `:bump` task during the release phase.


### Benefits

Rake is more than it appears at first glance.  It's not simply a Makefile with some Ruby features.  It _is_ Ruby; or rather a Ruby environment.  By putting Rake to good use, you avoid mangled Makefiles with pseudo-bash scripting baked in.

I also view this as a great mechanism for peer reviews / pull requests.  It allows the team visibility on what you're planning to release and why your chosen promotion level is appropriate (or inappropriate).  Simply dropping off a number in `VERSION` doesn't provide the same stimulus in my experience.

### The YAML File

Why YAML and not JSON or something else?  Meh.  I say do what you want!  I chose YAML because its human readable and ubiquitous in the DevOps realm lately.  You can also add additional fields at any time without breaking functionality.  In the above example I left `phase:` blank.  This is great for any modifiers such as `pre-release, alpha, beta, rc1, etc.`


### Requirements

Unlike the `yaml` gem, you will need to install `semantic` yourself.  Either use [bundler](https://bundler.io/) or simply run the following command:

```bash
$ gem install semantic
```
Additionally, this gem (and of course Ruby) will have to be installed on your CI/CD agent(s).  I would suggest using Bundler with a Gemfile to accomplish this.  If you're concerned about keeping your agents pristine, read up on installing the gems locally to the `vendor/` folder.

### Final Thoughts

The DevOps frontier is still very "wild west".  I'm sure someone will invent a tool that handles versioning in a more succinct way, but what would the fun be in that?