Running a Rails application in production requires installing Ruby and other packages. It’s not difficult and configuration management system like Chef or Ansible makes it even easier. Here we’re going to look at a different approach. By using Docker, installing Ruby and other packages happens on the build phase. In your production servers, you’ll run your Rails application like any other Docker containers.
Before you run your Rails application in a container, you need to build a Docker image. This is done using a Dockerfile.
The focus of this blog post is to present a Dockerfile that produces a production-ready Docker image.
Scale performance. Not price. Try Engine Yard today and enjoy our great support and huge scaling potential for 14 days.
Deploy your app for free with Engine Yard.
Dockerfile
FROM ruby:2.3
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y nodejs mysql-client postgresql-client sqlite3 vim --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV RAILS_ENV production
ENV RAILS_SERVE_STATIC_FILES true
ENV RAILS_LOG_TO_STDOUT true
COPY Gemfile /usr/src/app/
COPY Gemfile.lock /usr/src/app/
RUN bundle config --global frozen 1
RUN bundle install --without development test
COPY . /usr/src/app
RUN bundle exec rake DATABASE_URL=postgresql:does_
EXPOSE 3000
CMD ['rails', 'server', '-b', '0.0.0.0']
We create our app directory at /usr/src/app and set our WORKDIR to that directory.
We install different packages that are needed by our rails app. Nodejs is used for assets. A db client library is necessary but you don’t need all three. You can choose one among mysql-client, postgresql-client, and sqlite3. I like to install vim on my images in case I need to view config files on the container.
This Dockerfile is specifically for production so we need 3 environment variables. RAILS_ENV environment is set to production. RAILS_SERVE_STATIC_FILES is set to allow the app to serve static files which is otherwise disabled by default. RAILS_LOG_TO_STDOUT is set to log to standard out instead of a file.
We copy Gemfile and Gemfile.lock to /usr/src/app/ and run bundle install. We copy these 2 files separately before the rest of the app. Any changes on the source code, except for these 2 files, won’t trigger another bundle install. Due to caching, if Gemfile and Gemfile.lock are not changed, bundle install will be skipped.
We copy the rest of the app.
We compile the assets. When running the rake task, DATABASE_URL is required and we pass a dummy value.
We expose port 3000 which is the port our app will use inside the container.
We run our app using rails server.
After saving the Dockerfile on your app’s root directory, you can run docker build . -t your-dockerhub-username/image-
If you do not have a Rails app to dockerize, you can use Engine Yard’s todo app. Use the docker branch on my GitHub account at https://github.com/crigor/
To push it to Docker Hub run docker push your-dockerhub-username/image-
Database
In production, you can use RDS, a separate server, or even Docker if you know what you’re doing.
Back to my local machine, I use docker-machine on my Mac which runs a VM. I used the official postgres image from Docker Hub and forwarded port 5432 from the docker-machine VM to port 5432 on the container.
docker run -p 5432:5432 --name todo-postgres -e POSTGRES_PASSWORD=
Running your application
On your local machine, run your app by passing the 2 environment variables on the command line
docker run -p 3000:3000 -e DATABASE_URL=postgres://postgr
Putting credentials on environment variables is not secure though they definitely make things convenient. If you don’t want to use environment variables, you can put your database credentials on config/database.yml and the secret key base on config/secrets.yml.
Create them on the server where you’ll run your container and pass them to the container using volumes.
docker run -p 3000:3000 -v /path/to/secrets.yml:/usr/src/
Conclusion