Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deploy Rails application on Amazon Elastic Beanstalk on a single container Docker environment

I have been trying to dockerize my Rails application on Elastic Beanstalk. There are a lot of examples out there but most don't fit my specific use case. That is:

  • Running under a single container Docker environment (so no need for docker-compose/fig)
  • Run on Amazon Elastic Beanstalk.
  • Make use of passenger-docker as the base image (one of the Ruby variants).
  • Pass environment variables set by Elastic Beanstalk (either through CLI of console).
  • Nginx and Passenger in the container.
  • Ability to install custom packages (extend it).
  • Reasonable .dockerignore file.

The process on how to deploy is not the question here but rather the right Docker configuration that would work with Amazon Elastic Beanstalk with the above specific criteria.

What's the right configuration to get this running?

like image 363
King'ori Maina Avatar asked Jul 20 '15 12:07

King'ori Maina


1 Answers

This is what worked for me ...

Dockerfile

In this example I use phusion/passenger-ruby22:0.9.16 as the base image because:

  • Your Dockerfile can be smaller.
  • It reduces the time needed to write a correct Dockerfile. You won't have to worry about the base system and the stack, you can focus on just your app.
  • It sets up the base system correctly. It's very easy to get the base system wrong, but this image does everything correctly. Learn more.
  • It drastically reduces the time needed to run docker build, allowing you to iterate your Dockerfile more quickly.
  • It reduces download time during redeploys. Docker only needs to download the base image once: during the first deploy. On every subsequent deploys, only the changes you make on top of the base image are downloaded.

You can learn more about it here ... anyway, onto the Dockerfile.

# The FROM instruction sets the Base Image for subsequent instructions. As such,
# a valid Dockerfile must have FROM as its first instruction. We use
# phusion/baseimage as a base image. To make our builds reproducible, we make
# sure we lock down to a specific version, not to `latest`!
FROM phusion/passenger-ruby22:0.9.16

# The MAINTAINER instruction allows you to set the Author field of the generated
# images.
MAINTAINER "Job King'ori Maina" <[email protected]> (@itsmrwave)

# The RUN instructions will execute any commands in a new layer on top of the
# current image and commit the results. The resulting committed image will be
# used for the next step in the Dockerfile.

# === 1 ===

# Prepare for packages
RUN apt-get update --assume-yes && apt-get install --assume-yes build-essential

# For a JS runtime
# http://nodejs.org/
RUN apt-get install --assume-yes nodejs

# For Nokogiri gem
# http://www.nokogiri.org/tutorials/installing_nokogiri.html#ubuntu___debian
RUN apt-get install --assume-yes libxml2-dev libxslt1-dev

# For RMagick gem
# https://help.ubuntu.com/community/ImageMagick
RUN apt-get install --assume-yes libmagickwand-dev

# Clean up APT when done.
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# === 2 ===

# Set correct environment variables.
ENV HOME /root

# Use baseimage-docker's init process.
CMD ["/sbin/my_init"]

# === 3 ====

# By default Nginx clears all environment variables (except TZ). Tell Nginx to
# preserve these variables. See nginx-env.conf.
COPY nginx-env.conf /etc/nginx/main.d/rails-env.conf

# Nginx and Passenger are disabled by default. Enable them (start Nginx/Passenger).
RUN rm -f /etc/service/nginx/down

# Expose Nginx HTTP service
EXPOSE 80

# === 4 ===

# Our application should be placed inside /home/app. The image has an app user
# with UID 9999 and home directory /home/app. Our application is supposed to run
# as this user. Even though Docker itself provides some isolation from the host
# OS, running applications without root privileges is good security practice.
RUN mkdir -p /home/app/myapp
WORKDIR /home/app/myapp

# Run Bundle in a cache efficient way. Before copying the whole app, copy just
# the Gemfile and Gemfile.lock into the tmp directory and ran bundle install
# from there. If neither file changed, both instructions are cached. Because
# they are cached, subsequent commands—like the bundle install one—remain
# eligible for using the cache. Why? How? See ...
# http://ilikestuffblog.com/2014/01/06/how-to-skip-bundle-install-when-deploying-a-rails-app-to-docker/
COPY Gemfile /home/app/myapp/
COPY Gemfile.lock /home/app/myapp/
RUN chown -R app:app /home/app/myapp
RUN sudo -u app bundle install --deployment --without test development doc

# === 5 ===

# Adding our web app to the image ... only after bundling do we copy the rest of
# the app into the image.
COPY . /home/app/myapp
RUN chown -R app:app /home/app/myapp

# === 6 ===

# Remove the default site. Add a virtual host entry to Nginx which describes
# where our app is, and Passenger will take care of the rest. See nginx.conf.
RUN rm /etc/nginx/sites-enabled/default
COPY nginx.conf /etc/nginx/sites-enabled/myapp.conf

Dockerrun.aws.json

{
  "AWSEBDockerrunVersion": "1",
  "Ports": [
    {
      "ContainerPort": "80"
    }
  ],
  "Logging": "/home/app/myapp/log"
}

.dockerignore

/.bundle
/.DS_Store
/.ebextensions
/.elasticbeanstalk
/.env
/.git
/.yardoc
/log/*
/tmp

!/log/.keep

nginx-env.conf

Please note that rails-env.conf doesn't set any environment variables outside Nginx, so you won't be able to see them in the shell (i.e. the Dockerfile). You will have to use different methods to set environment variables for the shell too.

# By default Nginx clears all environment variables (except TZ) for its child
# processes (Passenger being one of them). That's why any environment variables
# we set with docker run -e, Docker linking and /etc/container_environment,
# won't reach Nginx. To preserve these variables, place an Nginx config file
# ending with *.conf in the directory /etc/nginx/main.d, in which we tell Nginx
# to preserve these variables.

# Set by Passenger Docker
env RAILS_ENV;
env RACK_ENV;
env PASSENGER_APP_ENV;

# Set by AWS Elastic Beanstalk (examples, change accordingly)
env AWS_ACCESS_KEY_ID;
env AWS_REGION;
env AWS_SECRET_KEY;
env DB_NAME;
env DB_USERNAME;
env DB_PASSWORD;
env DB_HOSTNAME;
env DB_PORT;
env MAIL_USERNAME;
env MAIL_PASSWORD;
env MAIL_SMTP_HOST;
env MAIL_PORT;
env SECRET_KEY_BASE;

nginx.conf

server {
  listen 80;
  server_name _;
  root /home/app/myapp/public;

  # The following deploys your app on Passenger.

  # Not familiar with Passenger, and used (G)Unicorn/Thin/Puma/pure Node before?
  # Yes, this is all you need to deploy on Passenger! All the reverse proxying,
  # socket setup, process management, etc are all taken care automatically for
  # you! Learn more at https://www.phusionpassenger.com/.
  passenger_enabled on;
  passenger_user app;

  # Ensures that RAILS_ENV, RACK_ENV, PASSENGER_APP_ENV, etc are set to
  # "production" when your application is started.
  passenger_app_env production;

  # Since this is a Ruby app, specify a Ruby version:
  passenger_ruby /usr/bin/ruby2.2;
}
like image 81
King'ori Maina Avatar answered Oct 25 '22 20:10

King'ori Maina