At the beginning of 2020, Tanis Steward, CEO of Sosido Networks Inc, asked Pieoneers to update Sosido’s aging Ruby on Rails application. The dated Ruby 2.3.1 stack the application was based on was no longer supported by the hosting platform Heroku.
Sosido is a global, multidisciplinary online knowledge-sharing network designed to speed-up sharing of clinical knowledge amongst healthcare professionals. Launched in 2011 and based out of Vancouver, Canada, there are currently tens of groups and tens of thousands of clinicians and researchers active on the Sosido network.
Ruby on Rails Application
The original application was developed about 5 years ago in Ruby on Rails. It was a solid solution back then, supported by the rich ecosystem and ready-to-go components for most typical web tasks.
The app runs two processes: a web server and a background worker. The web server provides access to the database of articles, fetched over RSS by the background worker from third-party medical data sources (e.g. Pubmed). New medical articles become available on Sosido minutes after publication on PubMed or other sources. The application provides a modern interface, so the users can easily navigate through the database and find the information they need.
Tanis received an alert from Heroku that they were about to drop Ruby 2.3.1 support soon. Unfortunately, that’s the latest version they had, so it was the right time to make the application compliant with the new Heroku requirements.
Even though Ruby on Rails has many benefits that make it an attractive alternative to other web frameworks, it offers poor compatibility for previous versions of the framework’s components. That's exactly why upgrading the legacy Ruby version of the application would take significant time and potentially substantial financial risk. We did our best to stay away from a complete rewrite while enabling the app to still run on Heroku.
The strategy we came up with was to wrap the existing codebase into a Docker container. An application launched in a container is essentially a black box with one or more interfaces exposed for connections. The outdated Ruby version, running in the virtual container was no longer a concern. This solution is widely adopted by thousands of companies in the industry and brings better reliability to production deployments.
First of all, we moved the existing code repository from GitHub to GitLab. Aside from our extensive experience with GitLab, the primary reason for this transition was the CI/CD tools we employ to build, test, and release Docker containers.
Let’s see how we configured a pipeline for the Sosido application through a GitLab CI manifest file:
- apk add curl nodejs bash
- curl https://cli-assets.heroku.com/install.sh | sh
- heroku container:login
- heroku container:push --recursive --app $HEROKU_APP --arg ASSET_HOST=$ASSET_HOST
- heroku container:release web worker --app $HEROKU_APP
The image above shows a GitLab manifest that details how the changed code, once committed, loads the updated application via the Docker container. This manifest contains all necessary instructions and steps for GitLab to process the update. The GitLab component, known as a runner, creates and operates a pipeline every time a new commit is pushed to a target branch. The pipeline builds, tests, and deploys containers to either staging or production Heroku apps for master or production target branches correspondingly.
Under the Hood
Let's take a closer look at the manifest. At the very beginning, we specify what Docker version the pipeline should run against. The image will become an environment container for all the following instructions. Using a specific version is strongly recommended because the image versions change over time, and can lead to unexpected failures of the integration processes in the future. The variables section contains the Docker connectivity settings. Further down in the manifest, the special Docker-in-Docker service is used for container builds. The rest of the manifest is fully dedicated to the deploy stage.
These are the steps it takes:.
1. Install dependencies (curl, nodejs, bash) and Heroku CLI that does the heavy lifting.
2. Authorize on Heroku. We use token authorization with a token stored as a GitLab CI/CD variable. This way the secret is never shared with anybody but listed administrators.
3. Build a Docker container image from the current commit.
4. Release the built image to Heroku registry.
5. Finally the manifest populates the new application image to Heroku runtime. The new app becomes live at this point.
Please note that some settings and variables are set according to the target branch. The staging and production images have slight differences. The configured pipeline takes about 5 minutes from a new commit received to a new version of the app available on the end-users on Heroku.
Please note that some settings and variables are set according to the target branch. The staging and production images have slight differences. The configured pipeline takes about 5 minutes from a new commit received to a new version of the app available on Heroku to the end-users.
After the successful containerization, we conducted a full quality assurance round. That included additional automated end-to-end tests and a few days of human testing. The old production deployment was employed as a point of reference, as it helped us check if the new containers were behaving as expected. We now have a set of client-verified plain-English test cases for future validation as well. With a green light from Tanis, we rolled the application out to production without a hiccup.
The bottom line here is that our client kept a mission-critical app on the same hosting platform despite the changed Ruby support policy, at a fraction of the full rewrite cost. The lesson here is that Docker containers are good for extending the life of aging applications.
When Heroku announced they would no longer support our version of Ruby, we were faced suddenly with a tight budget to meet a critical deadline. While other shops responded to our urgency with inflated prices, the Pieoneers team instead proposed a creative solution (containerizing) to meet the deadline within our budget. Pieoneers' communication throughout the process was exceptional; we met the upgrade deadline and happily continue to support the health professionals on Sosido to share their research and clinical knowledge. Pieoneers' experience with Ruby, and current work in a broad range of other programming languages, is a great asset to those of us looking to bring our valued Ruby apps into the future.