Skip to content

Instantly share code, notes, and snippets.

@cannikin
Last active March 28, 2019 16:08
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save cannikin/7d0834bc3814865a47df to your computer and use it in GitHub Desktop.
Save cannikin/7d0834bc3814865a47df to your computer and use it in GitHub Desktop.
Typical Ruby on Rails AWS Server Setup

Typical Ruby on Rails AWS Server Setup

You should be able to start a fresh EC2 instance of Ubuntu and follow the instructions below to get a server with your preferred version of Ruby, nginx ready to delegate requests to Unicorn, and logrotate setup to keep your disk from filling up with log files. You will also have ruby-install for installing new rubies and chruby for switching between them. A .ruby-version file will be added to the home directory of the user that runs this script.

Install

  1. Start an EC2 instance using the latest Ubuntu image (as of 2015-06-18 ami-5189a661 for EBS, 64-bit SSD)
  2. Copy the config files below into /tmp using the file names specified in the title.
  3. Edit /tmp/setup.sh and change the variables at the top to match your setup
  4. Make /tmp/setup.sh executable: chmod +x /tmp/setup.sh
  5. Run setup: /tmp/setup.sh
  6. ???
  7. Profit!

Notes

  • This script assumes the default directory structure created by capistrano deployments (/current and /shared directories exist under your app's install directory)
  • nginx will gzip files if they are bigger than 1000 bytes (and aren't already zipped)
  • This will work with the ELB HealthCheck even when it is terminating SSL
  • Assumes that unicorn's config file lives at config/unicorn/production.rb (default for the capistrano3-unicorn gem)
  • See comment about HTTP/2 in nginx.conf

See a short blog post about this script: http://ridingtheclutch.com/post/121847554610/my-preferred-ruby-on-rails-ec2-server-setup

#!/bin/sh
### BEGIN INIT INFO
# Provides: delayed_job
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the delayed_job server
# Description: starts delayed_job using start-stop-script
### END INIT INFO
set -e
USAGE="Usage: $0 <start|stop|restart>"
# app settings
USER="{{USER}}"
APP_NAME="{{APP_NAME}}"
APP_ROOT="{{INSTALL_PATH}}/$APP_NAME"
ENV="production"
# environment settings
CMD="cd $APP_ROOT/current && RAILS_ENV=$ENV bundle exec script/delayed_job -n {{DELAYED_JOB_WORKERS}}"
# make sure the app exists
cd $APP_ROOT || exit 1
case $1 in
start)
echo "Starting delayed_job..."
su - $USER -c "$CMD start"
;;
stop)
echo "Stopping delayed_job..."
su - $USER -c "$CMD stop"
;;
restart)
echo "Restarting delayed_job..."
su - $USER -c "$CMD restart"
;;
*)
echo >&2 $USAGE
exit 1
;;
esac
{{INSTALL_PATH}}/{{APP_NAME}}/shared/log/*.log {
su {{USER}} {{GROUP}}
daily
missingok
rotate {{KEEP_LOG_DAYS}}
compress
delaycompress
notifempty
copytruncate
}
upstream unicorn_server {
server unix:/tmp/unicorn.sock fail_timeout=0;
}
server {
listen 80;
listen 443 http2; # assuming SSL is enabled, you can use HTTP/2. This directive will not work before nginx 1.9.5
root {{INSTALL_PATH}}/{{APP_NAME}}/current/public;
server_name {{DOMAIN}};
gzip on;
gzip_min_length 1000;
gzip_types application/json text/css application/x-javascript;
sendfile on;
keepalive_timeout 65;
# maximum file upload size (keep up to date when changing the corresponding site setting)
client_max_body_size {{MAX_UPLOAD_SIZE}};
location / {
try_files $uri @app;
}
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
# Uncomment to implement rate limiting on a specific URL. Requires
# the following to be added to the global http{} block in nginx.conf:
#
# limit_req_zone $http_x_forwarded_for zone=one_per_second:50m rate=1r/s;
# limit_req_status 429;
#
# In this example only allow 1 req/sec for the given URL only:
#
# location /sensative_url {
# limit_req zone=one_per_second nodelay;
# try_files $uri @app;
# }
location @app {
if ($http_user_agent ~ "ELB-HealthChecker") {
set $http_x_forwarded_proto https;
}
error_page 404 /404.html;
error_page 500 /500.html;
error_page 502 /502.html;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
# pass to the upstream unicorn server mentioned above
proxy_pass http://unicorn_server;
}
}
USER=ubuntu # user that will run unicorn and delayed_job
GROUP=ubuntu # group that user belongs to
APP_NAME=myapp # name of directory that will contain your app
INSTALL_PATH=/var/www # parent directory of app
DOMAIN=myapp.com # domain name for nginx
RUBY_VERSION=2.2.2 # version of Ruby to install
KEEP_LOG_DAYS=7 # number of days logrotate will keep logs for
MAX_UPLOAD_SIZE=50m # max file size that nginx will allow to be uploaded
DELAYED_JOB_WORKERS=2 # number of delayed_job workers to run
# replace variables in setup files
sed -i s#{{APP_NAME}}#$APP_NAME#g /tmp/logrotate.conf
sed -i s#{{KEEP_LOG_DAYS}}#$KEEP_LOG_DAYS#g /tmp/logrotate.conf
sed -i s#{{INSTALL_PATH}}#$INSTALL_PATH#g /tmp/logrotate.conf
sed -i s#{{USER}}#$USER#g /tmp/logrotate.conf
sed -i s#{{GROUP}}#$GROUP#g /tmp/logrotate.conf
sed -i s#{{APP_NAME}}#$APP_NAME#g /tmp/nginx.conf
sed -i s#{{INSTALL_PATH}}#$INSTALL_PATH#g /tmp/nginx.conf
sed -i s#{{DOMAIN}}#$DOMAIN#g /tmp/nginx.conf
sed -i s#{{MAX_UPLOAD_SIZE}}#$MAX_UPLOAD_SIZE#g /tmp/nginx.conf
sed -i s#{{APP_NAME}}#$APP_NAME#g /tmp/unicorn.sh
sed -i s#{{INSTALL_PATH}}#$INSTALL_PATH#g /tmp/unicorn.sh
sed -i s#{{USER}}#$USER#g /tmp/unicorn.sh
sed -i s#{{APP_NAME}}#$APP_NAME#g /tmp/delayed_job.sh
sed -i s#{{INSTALL_PATH}}#$INSTALL_PATH#g /tmp/delayed_job.sh
sed -i s#{{USER}}#$USER#g /tmp/delayed_job.sh
# install essential ubuntu packages
sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y build-essential ntp nginx htop tmux git-core nodejs libmysqlclient-dev imagemagick pkg-config libmagickwand-dev
# install ruby-install
cd /tmp
wget -O ruby-install-0.7.0.tar.gz https://github.com/postmodern/ruby-install/archive/v0.7.0.tar.gz
tar -xzvf ruby-install-0.7.0.tar.gz
cd ruby-install-0.7.0/
sudo make install
# install chruby, add startup scripts in /etc/profile.d/chruby.sh
cd /tmp
wget -O chruby-0.3.9.tar.gz https://github.com/postmodern/chruby/archive/v0.3.9.tar.gz
tar -xzvf chruby-0.3.9.tar.gz
cd chruby-0.3.9/
sudo make install
printf "if [ -n \"\$BASH_VERSION\" ] || [ -n \"\$ZSH_VERSION\" ]; then\n source /usr/local/share/chruby/chruby.sh\n source /usr/local/share/chruby/auto.sh\nfi\n" > /tmp/chruby.sh && sudo mv /tmp/chruby.sh /etc/profile.d/chruby.sh && sudo chown root:root /etc/profile.d/chruby.sh
printf "$RUBY_VERSION\n" > ~/.ruby-version
# install ruby and use it
ruby-install ruby $RUBY_VERSION
source /usr/local/share/chruby/chruby.sh
chruby $RUBY_VERSION
# install bundler
gem install bundler --no-rdoc --no-ri
# setup web directory
sudo mkdir $INSTALL_PATH && sudo chown $USER:$GROUP $INSTALL_PATH
# add nginx config in /etc/nginx/sites-available/$APP_NAME.conf and setup link
sudo cp /tmp/nginx.conf /etc/nginx/sites-available/$APP_NAME && sudo chown root:root /etc/nginx/sites-available/$APP_NAME
sudo ln -nsf /etc/nginx/sites-available/$APP_NAME /etc/nginx/sites-enabled/$APP_NAME
# remove the default "nginx works!" site in nginx
sudo rm /etc/nginx/sites-enabled/default
# add logrotate config in /etc/logrotate.d/$APP_NAME
sudo cp /tmp/logrotate.conf /etc/logrotate.d/$APP_NAME && sudo chown root:root /etc/logrotate.d/$APP_NAME
# add unicorn init script in /etc/init.d/unicorn
sudo cp /tmp/unicorn.sh /etc/init.d/unicorn && sudo chown root:root /etc/init.d/unicorn && sudo chmod 755 /etc/init.d/unicorn
# set run levels for unicorn
sudo update-rc.d unicorn defaults
# add delayed_job init script in /etc/init.d/delayed_job
sudo cp /tmp/delayed_job.sh /etc/init.d/delayed_job && sudo chown root:root /etc/init.d/delayed_job && sudo chmod 755 /etc/init.d/delayed_job
# set run levels for delayed_job
sudo update-rc.d delayed_job defaults
printf "\n\n***********************\nInstall complete! Reboot to start services and clear memory.\n***********************\n"
#!/bin/sh
### BEGIN INIT INFO
# Provides: unicorn
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the unicorn app server
# Description: starts unicorn using start-stop-daemon
### END INIT INFO
set -e
USAGE="Usage: $0 <start|stop|restart|upgrade|rotate|force-stop>"
# app settings
USER="{{USER}}"
APP_NAME="{{APP_NAME}}"
APP_ROOT="{{INSTALL_PATH}}/$APP_NAME"
ENV="production"
# environment settings
CMD="cd $APP_ROOT/current && bundle exec unicorn -c config/unicorn/$ENV.rb -E $ENV -D"
PID="$APP_ROOT/shared/tmp/pids/unicorn.pid"
OLD_PID="$PID.oldbin"
# make sure the app exists
cd $APP_ROOT || exit 1
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $OLD_PID && kill -$1 `cat $OLD_PID`
}
case $1 in
start)
sig 0 && echo >&2 "Already running" && exit 0
echo "Starting $APP_NAME"
su - $USER -c "$CMD"
;;
stop)
echo "Stopping $APP_NAME"
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
echo "Force stopping $APP_NAME"
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload|upgrade)
sig USR2 && echo "reloaded $APP_NAME" && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
$CMD
;;
rotate)
sig USR1 && echo rotated logs OK && exit 0
echo >&2 "Couldn't rotate logs" && exit 1
;;
*)
echo >&2 $USAGE
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment