Bundler Bulkheads for Rails on Docker
As part of my exploration of a minimum set of devops tools, I’ve been learning how to stack containers full of Rails apps onto the Docker. There are plenty of examples of how to get started with Rails and Postgres on Docker, even one from the whale’s mouth, as it were. Working from this example, it was pretty clear to me that one of Docker’s major strengths is that it makes it really, really easy to get something running with a minimum of fuss; it took all of about a half day to learn enough Docker to hoist anchor, and even tweak a few things to my liking. One thing kept nagging me about the Docker example, though, and that was a warning from bundler when running docker-compose.
Oh Noes, a Warnthing?!
Don't run Bundler as root.
Running bundler in this way strikes me as a needless security risk (why sudo when you can suDON’T PRIVILEGE ESCALATION EXPLOIT YOURSELF), so I set about modifying the example file to:
- shut it up and…
- learn some more about Docker.
I’ll admit, depending upon your tolerance for warning messages, this kind of minutiae may not even show up on your radar. And let me be clear that this post is not a knock against the Docker team for this “flaw” in the example; they’re doing absolutely amazing stuff by building Docker in the first place and they absolutely give a damn about security. As mentioned in the intro, this is more about learning Docker by addressing this (arguably security-oriented) warning.
The Example From the Dockermentation
For reference, the example Dockerfile looks like so:
FROM ruby:2.2.0 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev RUN mkdir /myapp WORKDIR /myapp ADD Gemfile /myapp/Gemfile ADD Gemfile.lock /myapp/Gemfile.lock RUN bundle install ADD . /myapp
Let’s unpack this a bit before moving on (for a more in-depth explanation, head over to the docs themselves):
- Pull the base image for Ruby 2.2.0.
- Update and install some packages with apt-get.
- Make a new directory and work out of it.
- Copy over a seed Gemfile(.lock).
- Install with bundler (THE OFFENDING LINE (O_O) ).
- “Bake” the now-bundled app into the container.
Look At Me I Can Docker Too
Sans fanfare, the modified Dockerfile in all its glory:
FROM ruby:2.2.3 RUN apt-get update -qq RUN apt-get install -y build-essential libpq-dev RUN mkdir /app WORKDIR /app ADD . /app RUN useradd -ms /bin/bash rails RUN chown -R rails:rails . USER rails ENV GEM_HOME /home/rails/.gems RUN gem install bundler RUN bundle
How does this differ from the documented example? Well:
- A rails user is created and used to install gems with bundler and then run the application. No More Sudo Bundle. That being said, this still needs to be updated to include something like Nginx as a proxy to get around the limitation of being unable to start Rails on port 80 without sudo. To be addressed…
- The Rails app is “newed” on the host machine prior to spinning up the app for the first time. This avoids the need to create a placeholder Gemfile as shown in the Docker example, and IMHO, feels like it more clearly separates the containerization step from the source code implementation step.
And there you have it. With a few minor tweaks to the Dockerfile, bundler has been silenced, and the deployment configuration uses a dedicated, unprivileged user for managing package installation and starting the relevant app. What’s more is that it accomplishes this while both leveraging the base Ruby image on Docker Hub and avoiding the overhead of RVM or rbenv. All in all, this very particular goal really helped to clarify my understanding of Docker, from permissions all the way to source code copying. Not least of all, this leaves me feeling a bit more at-ease, knowing that there is one less way to succumb to some as-yet unknown vulnerability in Docker whilst running errant sudo commands in Dockerfiles.