Please forgive my strong opinion here: while we built Cloud 66 with Rails and ran it in production in containers and I can tell you with conviction, I dislike Docker's developer experience just as much as I like RoR's. The contrast couldn't be starker.
For almost all of us who build Rails applications, Docker is an excellent solution in production but a pain in the neck when it comes to the development environment.
If you're wondering, here is a short list of the challenges of running Rails in Docker:
- What's the best way to build an image? Every developer for themselves? A base image shared with the team?
- Where to put the code? In the container or mounted as a volume? (BTW, is it only me, or do you have to google Docker Volume syntax every time?)
- Issues with Node.js and compiling assets, webpacker, and running webpacker development server in containers.
- Docker Compose failing to shutdown containers randomly and consuming four times more resources compared to running Rails web and Sidekiq outside containers.
- Have a private gem? Welcome to the nightmare of SSH key sharing inside of a container image.
On the other hand, using containers (specifically with Kubernetes) for Rails makes deployments significantly faster, more reliable, and lets you rollback deployments with little effort or stress.
Many everyday operational tasks like backups, scaling, load balancing, and creating a stack per branch improved significantly with Kubernetes (I admit there is a steep learning curve involved, but I guess, "no pain, no gain!").
For these reasons, we decided to stay with the native Rails development experience while running our non-development environments on Kubernetes. This presented a new challenge around configuration management for Rails. In Rails, configuration usually lives in YAML files, while in Kubernetes configuration settings are split one per file (using ConfigMaps or Secrets). We wanted a solution that could load configuration settings from a single YAML file or multiple individual files seamlessly.
The result of this effort is a new open-source Gem called Konfig. Here is what Konfig can do:
- Load configuration from YAML or single-value files and present them to the application in a
Settings.foo.bar
syntax. - Verify configuration against strongly-typed validation before the application starts.
- Support environment variable overrides (very useful in containerized environments)
- Support JSON as a native data type inside YAML.
- Support ERB with its YAML configuration files.
Configuration (most likely as an initializer)
if %w(development test).include?($environment)
Konfig.configuration.mode = :yaml
Konfig.configuration.workdir = File.join($rails_root, 'config', 'settings')
# set the konfig log level to WARN
konfig_logger = ActiveSupport::Logger.new(STDOUT)
konfig_logger.level = ::Logger::WARN
Konfig.configuration.logger = konfig_logger
else
Konfig.configuration.mode = :directory
# this is mounted by Kubernetes from ConfigMap or Secrets
Konfig.configuration.workdir = "/app/config/settings/configuration"
end
Konfig.configuration.schema do
required(:foo).schema do
required(:bar).filled(:string)
required(:buzz).filled(:string)
required(:fuzz).filled(:boolean)
end
end
Usage:
puts Settings.foo.bar
You can find Konfig here. Let us know how you're using it and how it can be improved!