You can find a more in-depth Blog Post Here
This is a sample Rails 6.1 app with 2 Ansible Playbooks, inside .ansible-deploy
folder. Provision playbook is used to easily provision an Ubuntu 20.04 Server, there's another playbook for deployment and rollback your Rails app using Ansistrano.
While this is meant to work out of the box, you can tweak the files in the roles
directory in order to satisfy your project-specific requirements.
- Configure Ubuntu 20.04 Server with some sensible defaults.
- Install required/useful packages. You can update this list here.
- Auto upgrade all installed packages
- Create a new deployment user (called 'deploy') with passwordless login
- SSH hardening
- Prevent password login
- Change the default SSH port
- Prevent root login
- Setup UFW (firewall)
- Setup Fail2ban
- Install Logrotate
- Setup Nginx with some sensible config (thanks to nginxconfig.io)
- Certbot, for Let's encrypt SSL certificates.
- Ruby (using Rbenv).
- Defaults to
2.7.2
. You can change it in theapp-vars.yml
file - jemmaloc is also installed and configured by default
- rbenv-vars is also installed by default
- Defaults to
- Node.js
- Defaults to 15.x. You can change it in the
app-vars.yml
file.
- Defaults to 15.x. You can change it in the
- Yarn
- Redis (latest)
- Postgresql.
- Defaults to v13. You can specify the version that you need in the
app-vars.yml
file.
- Defaults to v13. You can specify the version that you need in the
- Puma (with Systemd support for restarting automatically) See Puma Config section below.
- Sidekiq (with Systemd support for restarting automatically)
- Ansistrano hooks for performing the following tasks -
- Installing all our gems
- Precompiling assets
- Migrating our database (using
run_once
)
Here are the steps that you need to follow in order to get up and running with Ansible Rails.
You can just copy the .ansible-deploy
folder in your Rails application folder, or clone this repo.
As mentioned earlier, we have one Ansible Playbook to setup the serve, so the secret variables that are needed to setup the server, are stored in an Ansible Vault. The secrets related to the Rails app, should be stored using Custom Credentials.
To create a new Ansible Vault
(https://docs.ansible.com/ansible/latest/user_guide/vault.html) file to store sensitive information:
$ ansible-vault create .ansible-deploy/group_vars/all/vault.yml
Add the following information to this new vault file
vault_postgresql_db_password: "XXXXX_SUPER_SECURE_PASS_XXXXX"
vault_rails_master_key: "XXXXX_MASTER_KEY_FOR_RAILS_XXXXX"
Configure the relevant variables in app-vars.yml
.
app_name: YOUR_APP_NAME # Replace with name of your app
app_git_repo: "YOUR_GIT_REPO"
app_git_branch: "main" # branch that you want to deploy (e.g: 'production')
postgresql_db_user: "{{ deploy_user }}_postgresql_user"
postgresql_db_password: "{{ vault_postgresql_db_password }}" # from vault (see previous section)
postgresql_db_name: "{{ app_name }}_production"
If you have booted up a clean Ubuntu Server, you can install all the dependencies for your Rails application running:
$ cd .ansible-deploy
$ ansible-playbook -i inventories/development.ini provision.yml
In Ansible, an inventory is called the list of server(s) where you want to run your playbook. If you want to use a different staging / production servers, you can have 2 inventory files one for each stage, called staging.ini
/ production.ini
.
To deploy this app to your production server, you can use DigitalOcean, AWS or any other hosting provider of your preference.
[web]
192.168.0.1 # replace with IP address of your server.
[all:vars]
ansible_ssh_user=deployer
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_private_key_file="~/.ssh/id_rsa"
After your server is setup (step 4), you can deploy your application running:
$ cd .ansible-deploy
$ ansible-playbook -i inventories/development.ini deploy.yml
You can add/remove packages to this list by changing the required_package
variable in app-vars.yml
.
Uncomplicated Firewall is enabled and accepting connections from any IP on ports 22 (ssh), 80 (http), and 443 (https). Feel free to update it in app-vars.yml
.
Certboot is configured to request, install and set up a CRON job to update your certificate when it expires. Setup your domain DNS as well as the details for your domain / email to request the certificate in app-vars.yml
.
By default, daily backup is enabled in the app-vars.yml
file. In order for this to work, the following variables need to be set. If you do not wish to store backups, remove (or uncomment) these lines from app-vars.yml
.
aws_key: "{{ vault_aws_key }}" # store this in group_vars/all/vault.yml that we created earlier
aws_secret: "{{ vault_aws_secret }}"
postgresql_backup_dir: "{{ deploy_user_path }}/backups"
postgresql_backup_filename_format: >-
{{ app_name }}-%Y%m%d-%H%M%S.pgdump
postgresql_db_backup_healthcheck: "NOTIFICATION_URL (eg: https://healthcheck.io/)" # optional
postgresql_s3_backup_bucket: "DB_BACKUP_BUCKET" # name of the S3 bucket to store backups
postgresql_s3_backup_hour: "3"
postgresql_s3_backup_minute: "*"
postgresql_s3_backup_delete_after: "7 days" # days after which old backups should be deleted
Update your config/puma.rb with settings for production.
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
port ENV.fetch("PORT") { 3000 }
rails_env = ENV.fetch("RAILS_ENV") { "development" }
environment rails_env
if %w[production staging].member?(rails_env)
app_dir = ENV.fetch("APP_DIR") { "YOUR_APP/current" }
directory app_dir
shared_dir = ENV.fetch("SHARED_DIR") { "YOUR_APP/shared" }
# Logging
stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true
pidfile "#{shared_dir}/tmp/pids/puma.pid"
state_path "#{shared_dir}/tmp/pids/puma.state"
# Set up socket location
bind "unix://#{shared_dir}/sockets/puma.sock"
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
preload_app!
elsif rails_env == "development"
# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
worker_timeout 3600
plugin :tmp_restart
end
These 2 repos were a useful inspiration and starting point: