The baseline setup:
- Nginx will be used to reverse proxy to the Rails app (run by unicorn/thin/...)
- A non-privileged user is created, which will be running the app and used for safe deployments
- For this user, we'll install:
- rbenv (https://github.com/sstephenson/rbenv) to manage multiple Ruby versions
- ruby-build (https://github.com/sstephenson/ruby-build) to automate the Ruby build
- rbenv-vars (https://github.com/sstephenson/rbenv-vars) to manage environment variables for Ruby processes
- gem updates:
gem update --system
- bundler (http://gembundler.com/)
The setup.sh
example script may help you to automate these initial steps.
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.
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.
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.
Hint: I've reused a decent amount from the script to bootstrap a vagrant box (through the
:shell
provisioner) which works pretty well.