For a while, I am tinkering with the idea to push the pet vs cattle analogy a little further. In most cases, I pick some docker container for my current purpose and link the app into the image. But I like the idea to kill the app too. In the end, only user-generated content left in a DB or as files.

I found a good example on GitHub from sameersbn. He created a docker container for several systems. I adapted his work:

# docker build -t rails-app:latest .
# get ruby base container
FROM ruby:2.3.1
# maintainer
MAINTAINER maintainer mail

# defaults
# change APP_VERSION for rebuild
ENV RAILS_USER="rails" \
    RAILS_HOME="/home/rails" \
    RAILS_LOG_DIR="/var/log/rails" \
    RAILS_CACHE_DIR="/etc/docker-rails" \
    RAILS_ENV=production \
    MYGITSERVER=“some.git.server” \
    REPO=“path.to.git” \
    APP_VERSION=“1.0"

ENV RAILS_INSTALL_DIR="${RAILS_HOME}/app" \
    RAILS_DATA_DIR="${RAILS_HOME}/data" \
    RAILS_BUILD_DIR="${RAILS_CACHE_DIR}/build" \
    RAILS_RUNTIME_DIR="${RAILS_CACHE_DIR}/runtime"

# expose for proxy or host directly
EXPOSE 80

# install software for your app
RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y supervisor logrotate nodejs sudo \
        libsqlite3-dev nginx mysql-client postgresql-client \
        imagemagick git bzr darcs rsync locales \
        gcc g++ make patch pkg-config gettext-base libc6-dev zlib1g-dev libxml2-dev \
        libpq5 libpq-dev libyaml-0-2 libcurl3 libssl1.0.0 libmysqlclient18 \
        libxslt1.1 libffi6 zlib1g gsfonts ssh \
    && update-locale LANG=C.UTF-8 LC_MESSAGES=POSIX \
    && gem install thin

# create user - app should not run as root
RUN adduser --disabled-login --gecos 'rails' $RAILS_USER && \
    passwd -d $RAILS_USER && \
    usermod -a -G sudo $RAILS_USER
# prepare ssh
# I use a private repo with own deploy key. 
COPY assets/ssh "/root/.ssh"
RUN chmod -R 600 "$HOME/.ssh"
# Create known_hosts
RUN touch "$HOME/.ssh/known_hosts"
# Add key
RUN ssh-keyscan $MYGITSERVER >> "$HOME/.ssh/known_hosts"

# pull system
RUN git clone $REPO $RAILS_INSTALL_DIR
# add local gem to add thin or unicorn
COPY assets/rails/Gemfile.local "$RAILS_INSTALL_DIR/Gemfile.local"
# bundle install
RUN cd $RAILS_INSTALL_DIR && \
    bundle install --without development test

# copy configs
COPY assets/nginx/nginx.conf /etc/nginx/nginx.conf
COPY assets/rails/database.yml "$RAILS_INSTALL_DIR/config/database.yml"
COPY assets/rails/thin.yml "$RAILS_INSTALL_DIR/config/thin.yml"
COPY entrypoint.sh /sbin/entrypoint.sh
RUN chmod 755 /sbin/entrypoint.sh

# own home
RUN chown -R "$RAILS_USER:$RAILS_USER" "$RAILS_HOME"

# prepare run
WORKDIR "$RAILS_INSTALL_DIR"
ENTRYPOINT ["/sbin/entrypoint.sh"]

And that is it. I use continuous integration to build the docker container automatically on master push.

The container is hosting the rails app via thin or unicorn over a socket to a Nginx. The Nginx runs in the foreground to avoid that the container will shut down immediately.

The entry point will do some updates before starting the server.

# create secret token
echo "Set secret..."
cd $RAILS_INSTALL_DIR && export SECRET_TOKEN=$(sudo -u $RAILS_USER -HE bundle exec rake secret)
cd $RAILS_INSTALL_DIR && export SECRET_KEY_BASE=$(sudo -u $RAILS_USER -HE bundle exec rake secret)
cd $RAILS_INSTALL_DIR && export DEVISE_SECRET_KEY=$(sudo -u $RAILS_USER -HE bundle exec rake secret)
# database
echo "Check db..."
cd $RAILS_INSTALL_DIR && sudo -u $RAILS_USER -HE bin/rake db:create
cd $RAILS_INSTALL_DIR && sudo -u $RAILS_USER -HE bin/rake db:migrate
# precompile
echo "Precompile assets..."
cd $RAILS_INSTALL_DIR && sudo -u $RAILS_USER -HE bin/rake assets:precompile
#start thin
echo "start thin server..."
cd $RAILS_INSTALL_DIR && sudo -u $RAILS_USER -HE bundle exec thin start -C "config/thin.yml"
echo "start nginx server..."
service nginx start