Moving a Rails monolith to Docker
I had some downtime at the end of 2017, so I decided to take a stab at moving our seven-year-old Rails monolith to Docker.
The motivation behind it was that I’ve got a new machine and setting up our current stack would take several hours plus I would have to help the rest of the team to set up their environment as well so perfect excuse to Dockerize it!
There are plenty of tutorials on the web about this topic so I’m not going to get into details about installation and basic Docker usage I mostly want to describe the configuration that worked for us and you can take bits and pieces to build your own.
The first thing to create is the Dockerfile most of it is pretty standard like selecting a Ruby image, install package dependencies and occasionally fix some esoteric issues like on line 13 with ImageMagick, the main difference from this Dockerfile is that it doesn’t run anything it just sets the stage for docker-compose to put things together.
The beauty of Docker is that you can pull other images for services you need like Redis, Mysql, and Memcached and glue them using docker-compose no messy config, define once run anywhere.
Another neat feature is that you can define volumes for your services and data inside those volumes will be persisted even if you shutdown and rebuild your container — lines 15, 16. Also, service names like redis-docker, memcache-docker and mysql-docker become a hostname inside your app which is pretty handy and elegant solution for dependencies in the code.
The rails-app entry on line 23 puts everything together, command on line 31 triggers a shell script to boot up the rails app, more on this later. Line 38 declares three separate volumes one for the code base so you can make changes using our favorite editor and it will reflect changes in the container, bundle-cache, as the name says, saves gems in a volume so restarting or rebuilding the container won’t try to install plugins all over again.
One huge pain when using Docker for Mac is the terribly slow IO performance when you use volumes making it unusable for development unless you use a tool like docker-sync, setting it up is simple you need a docker-sync.yml where you define the src entry point line 6 and the name (line 5) that’s going to be used as an external volume reference in docker-compose lines (39, 43).
start_dev.sh (docker-compose line 31) runs bundle install if needed and call foreman to launch Rails, Sidekiq, Solr and Compass to compile Sass to CSS on the fly.
To start the container instead of docker-composer up you run docker-sync-stack start and wait a few seconds for everything to launch!
Workflow :
- docker-sync-stack start
- docker-compose exec rails-app rails c
- docker-compose exec rails-app bash
- docker-compose exec rails-app rake db:migrate
- docker-compose exec rails-app env RAILS_ENV=test rspec [file]
This setup has been stable and working pretty well for us, time to provision a new dev environment dropped from several hours to less than 30 min, having Docker as part of the stack also makes it easier to upgrade stuff and if you screw up you can just rebuild the container and you’re up and running again!
Cheers!