Skip to content

Instantly share code, notes, and snippets.

@pmeinhardt
Created November 17, 2012 18:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmeinhardt/4098552 to your computer and use it in GitHub Desktop.
Save pmeinhardt/4098552 to your computer and use it in GitHub Desktop.
clean rails server setup

Server setup

Basic setup

The baseline setup:

  1. Nginx will be used to reverse proxy to the Rails app (run by unicorn/thin/...)
  2. A non-privileged user is created, which will be running the app and used for safe deployments
  3. For this user, we'll install:
  4. rbenv (https://github.com/sstephenson/rbenv) to manage multiple Ruby versions
  5. ruby-build (https://github.com/sstephenson/ruby-build) to automate the Ruby build
  6. rbenv-vars (https://github.com/sstephenson/rbenv-vars) to manage environment variables for Ruby processes
  7. gem updates: gem update --system
  8. bundler (http://gembundler.com/)

The setup.sh example script may help you to automate these initial steps.

Why do it like this?

Nginx offers you a lot of flexibility, whether you're letting it serve static files, delegate to your Rails or Django app, load balance… It doesn't care what you put it in front of. The configuration is pretty easy on the eyes (especially when compared to Apache).

Using rbenv together with the ruby-build plugin, allows you to quickly install Ruby, keep it updated and even use different versions for your different Rails projects on the same server. Just make sure to commit your .rbenv-version file.

In combination with rbenv-vars, rbenv allows you to externalize instance-specific configuration settings. Thus it eliminates a huge part of copying and adapting template configuration files… Instead you can push your config files with the rest of your code and have it read the values from the environment. This is especially useful if you have multiple instances of your app, e.g. for testing, staging.

Example Configuration

Rails config: database.yml and other config files

The beauty of using rbenv-vars is, that we can easily maintain different installations of an app without having to adapt any configuration files, i.e. database.yml.

You could simply use a config file like the following and check this into your SCM without worrying about secret passwords and the like:

# config/database.yml

defaults: &defaults
  adapter: sqlite3
  timeout: 5000
  pool: 5

development:
  <<: *defaults
  database: db/development.sqlite3

test: &test
  <<: *defaults
  database: db/test.sqlite3

cucumber:
  <<: *test

production:
  adapter: postgresql
  database: app-production-db
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['DB_PASS'] %>
  encoding: unicode
  pool: 5

You can set DB_USER=… and DB_PASS in a .rbenv-vars file or the ~/.rbenv/vars that we created in setup.sh. Just make sure to chmod go-rwx it, if it contains sensitive data.

You could, for instance also use this approach to set FB_APPID and FB_SECRET or other credentials in the environment of your Rails application.

Nginx vhost

For Nginx to reverse-proxy to your Rails application, you'll need to configure a vhost (most likely in /etc/nginx/sites-available and symlinked into /etc/nginx/sites-enabled), similar to the following example.

This example vhost assumes, you're deploying with Capistrano or a similar tool and have a /var/www/<app-name> directory present. This could be symlinked to a ~/<app-name> directory in the non-privileged user's $HOME, so you can set this writable directory as the deploy_to target for Capistrano.

Furthermore, the example configuration was derived from an app served through Unicorn. If you're using Thin or another Rack-compatible server, adapt the upstream accordingly.

# /etc/nginx/sites-available/<app-name>

upstream rails {
  server unix:/var/www/<app-name>/shared/sockets/unicorn.sock;
}

server {
  listen 80;
  listen 443 ssl;
  server_name www.<domain>;
  include /etc/nginx/<domain>-ssl.conf;
  rewrite ^ https://<domain>$request_uri? permanent;
}

server {
  listen 80;
  listen 443 ssl;

  server_name <domain>;

  root /var/www/<app-name>/current/public;
  index index.html index.htm;

  # Configure SSL certificates and ciphers.
  include /etc/nginx/<domain>-ssl.conf;

  # Configure gzip support.
  gzip_static on;
  gzip_vary on;
  gzip_types application/x-javascript text/css application/json;
  gzip_comp_level 5;
  gzip_proxied any;

  location / {
    # First attempt to serve request as file, then
    # as directory, then reverse proxy to rails.
    try_files $uri $uri/ @rails;
  }

  # Reverse proxy to rails.
  location @rails {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://rails;
  }
}

The unicorn.rb was an adapted version of https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.rb.

#!/bin/bash
# Need to run as root for this to work
if [ $UID -ne "0" ]; then
echo "You need to run this script as root: sudo bash $0"
exit 1
fi
# Configure the setup
USERNAME="app"
GROUPNAME=$USERNAME
RUBYVER="1.9.3-p327"
# Install dependencies
apt-get update
apt-get install git-core build-essential
apt-get install zlib1g-dev libssl-dev libreadline5-dev
# for newer ubuntus, use: libreadline6-dev
apt-get install python-software-properties
apt-get upgrade
# Install an up-to-date Nginx if it's not installed
if ! $(which nginx &> /dev/null); then
add-apt-repository ppa:nginx/stable
apt-get install nginx
fi
# Create a non-privileged user for running our Rails app
adduser --quiet --disabled-password $USERNAME
# Install rbenv, ruby-build and setup rbenv-vars
sudo -H -u $USERNAME bash <<-EOS
git clone --quiet git://github.com/sstephenson/rbenv.git ~/.rbenv
# Install rbenv plugins
mkdir -p ~/.rbenv/plugins
git clone --quiet git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
git clone --quiet git://github.com/sstephenson/rbenv-vars.git ~/.rbenv/plugins/rbenv-vars
touch ~/.rbenv/vars
chmod go-rwx ~/.rbenv/vars
# Hook rbenv into .bashrc
if ! grep -q "rbenv" ~/.bashrc; then
echo '' >> ~/.bashrc
echo '# Initialize rbenv' >> ~/.bashrc
echo 'export PATH=\$PATH:~/.rbenv/bin' >> ~/.bashrc
echo 'eval "\$(rbenv init -)"' >> ~/.bashrc
fi
EOS
# Install Ruby, update Gems, install bundler
sudo -H -u $USERNAME bash <<-EOS
export PATH=~/.rbenv/bin:\$PATH
eval "\$(rbenv init -)"
# Build the specified Ruby
if ! rbenv versions | grep -q $RUBYVER; then
rbenv install $RUBYVER
fi
rbenv global $RUBYVER
gem update --system
gem install bundle
EOS
@pmeinhardt
Copy link
Author

Hint: I've reused a decent amount from the script to bootstrap a vagrant box (through the :shell provisioner) which works pretty well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment