This is a starter set of Ansible playbooks for deploying a Thredded app to an Ubuntu 18.04 server.
Warning: Experimental, there be dragons.
These playbooks provide a good starting point if you're looking to deploy Thredded to a VPS or to a bare-metal server.
TODO:
- Email via postfix.
- Backups via backup.
There are two playbooks included: provision.yml and deploy.yml.
The provisioning playbook must be run as a user with password-less sudo access.
This playbook ensures the following:
Via the webapp role:
- System-wide dependencies are installed and are up-to-date.
- The app-specific user exists.
- The app directory (
/var/www/$APPby default) exists and belongs to the app-specific user.
Via the db role:
- The Postgresql database server is set up and has a user for the app.
Via the memcached role:
- The memcached server is set up (by default, on the same machine(s) as the webserver(s)).
Via the nginx role:
- The nginx webserver is set up and the app's site configuration is up to date.
Via the foreman_systemd role:
- The app services are managed by
systemdand their configuration is exported via foreman fromfiles/Procfile. - The app-specific user has permissions to start and stop app services.
Via the letsencrypt role (production only):
- Let's Encrypt is configured to provide and automatically renew the SSL certificate for the app's webserver.
The deployment playbook is run as the app-specific user.
This playbook ensures the following via the deploy-rails role:
- The configured Ruby version is installed (via
rbenv). - The app's environment variables are up to date (set in
~/.pam_envinoment).
The playbook then fetches the Rails app code from the configured git repository, compiles the assets, runs the migrations, and restarts the app services.
These playbooks work out of the box with apps created with thredded_create_app v0.1.10+.
If your app was created with an older version of thredded_create_app, or was not created with thredded_create_app, you'll need to:
-
Copy the config/puma.production.rb file.
-
Add the following snippet to the
config/environments/production.rbfile:if ENV['MEMCACHE_SERVERS'] config.cache_store = :dalli_store, ENV['MEMCACHE_SERVERS'].split(','), { namespace: ENV['APP_ID'] || raise("ENV['APP_ID'] not set"), socket_timeout: 1.5, socket_failure_delay: 0.2, down_retry_delay: 60, pool_size: [2, ENV.fetch('WEB_CONCURRENCY', 3).to_i * ENV.fetch('MAX_THREADS', 5).to_i].max } end
-
Ensure that the
config/database.ymlfile usesENV['DATABASE_URL']in production, i.e.:
production:
url: <%= ENV['DATABASE_URL'].inspect if ENV['DATABASE_URL'] %>First, clone this repo.
git clone https://github.com/thredded/thredded-ansible myapp-ansible
cd myapp-ansibleThen, copy the example variables file:
# Set the APP env var to your app name. Will be used throughout the Readme.
APP=myapp
# Copy the example variables file:
cp vars/example.yml "vars/${APP}.yml"Then, set the required variables in the variables file.
Also, have a look at the vars/defaults.yml for the optional variables and
their defaults.
Finally, see the section below to try out your setup in a VM.
Once you've confirmed it's working in a VM, you can proceed to set up and deploy to a real server or a VPS. The Readme section for this is not ready yet.
First, install VirtualBox v5.0+ and Vagrant v1.9+.
Then, create a new instance with:
vagrant upThen, provision the VM instance:
ansible-playbook provision.yml -e "config=vars/${APP}.yml" -u ubuntu --private-key=.vagrant/machines/vm1/virtualbox/private_keyIf you get an SSH error, verify that you can
SSH into the host as the ubuntu user.
Now, add the app user SSH key to the app's Git repository deploy keys.
-
Print the key with:
ansible webservers -u $APP -a 'cat ~/.ssh/id_rsa.pub'
-
Add the key to the deploy keys in your repository settings.
On GitLab, the deploy keys settings page is at:
https://gitlab.com/$USER/$APP/deploy_keysOn GitHub, it's at:
https://github.com/$USER/$APP/settings/keys
Then, deploy to the VM instance:
ansible-playbook deploy.yml -e "config=vars/${APP}.yml"This will run the migrations, but you might also want to seed the database if deploying for the first time. You can do so by running:
ansible 'webservers[0]' -u $APP -a "chdir=/var/www/$APP/current bundle exec rails db:seed"Congratulations! You can now open the app at http://localhost:8080 and log in as
admin@$APP.com with password 123456.
The guide describes provisioning deploying to a single production server.
The process is largely identical to deploying to Vagrant, but we will also:
- Enable HTTPS via letsencrypt with automated certificate renewal.
- Enable daily database backups using backup. TODO
- Configure the postfix mail transfer agent to send email. TODO
This guide focuses on the differences from the VirtualBox guide above and assumes that you have already followed the VirtualBox guide.
First, we need to create the hosts inventory file for our production hosts:
-
Copy the example hosts file:
cp hosts/example hosts/${APP}-prod -
Set the host IP address in the
hosts/${APP}-prodfile.
We will need to tell Ansible to use this inventory file by passing
-i hosts/${APP}-prod to every ansible and ansible-playbook command.
Alternatively, you can change the default inventory file in ansible.cfg.
If you've created a new server with something like Scaleway (offers a 3 € a month VPS large enough for Thredded) then the server by default will only have a root user with SSH key authentication, so you can skip this step.
If you do not have password-less SSH-as-root, or you'd prefer to not use the root user (e.g. if you work in a team), you can create another user instead like this:
-
Create a user with the same name as your local user on the production servers. Omit the
passwordargument if you're sure you'll never lose your SSH key.ansible all -i hosts/${APP}-prod -u root -m user -a \ "name=$USER groups=sudo password=$(mkpasswd --method=sha-512)"
-
Authorize your SSH key to connect as
$USER:ansible all -i hosts/${APP}-prod -u root -m authorized_key -a \ "user=$USER key={{lookup('file', '~/.ssh/id_rsa.pub')}}"
-
Allow the newly created user to run
sudowithout password:ansible all -i hosts/${APP}-prod -u root -m lineinfile -a \ "path=/etc/sudoers.d/$USER create=yes line='$USER ALL=(ALL) NOPASSWD:ALL' validate='/usr/sbin/visudo -cf %s'"
-
Pass
-u $USERinstead of-u rootto Ansible in the Provisioning commands below.
Copy the vars config file you've created earlier for Vagrant, as we'll need the configuration to be slightly different (we'll change it afterwards).
cp vars/${APP}.yml vars/${APP}-prod.ymlYou may want to change all the passwords and the secret key in the new config.
Then, run the provisioning playbook:
ansible-playbook provision.yml -u root -e "config=vars/${APP}-prod.yml" -i hosts/${APP}-prod-
Add the SSH key of the app user to your git repository deploy keys. To print the key, run:
ansible webservers -i hosts/${APP}-prod -u $APP -a 'cat ~/.ssh/id_rsa.pub'
-
Run the deployment playbook:
ansible-playbook deploy.yml -i hosts/${APP}-prod -e "config=vars/${APP}-prod.yml"
-
Seed the database:
ansible 'webservers[0]' -i hosts/${APP}-prod -u $APP -a "chdir=/var/www/$APP/current bundle exec rails db:seed"
Congratulations! You can now open the app at the server's IP and log in as
admin@$APP.com with password 123456. Change the password immediately.
Next, we will configure HTTPS with Let's Encrypt, including certificate auto-renewal.
For this, you will need to have {{app_host}} and www.{{app_host}}
configured as the A-records for your server IP.
First, in the vars/${APP}-prod.yml file, set the app_host and uncomment the
HTTPS with Let's encrypt section.
Then, run the provisioning playbook:
ansible-playbook provision.yml -u root -e "config=vars/${APP}-prod.yml" -i hosts/${APP}-prodThat's it! Verify that https://{{app_host}} works, and that these variations
all redirect to that URL: http://{{app_host}}, http://www.{{app_host}},
and https://www.{{app_host}}.
Test the quality of your HTTPS configuration at SSL Labs.
TODO
You'll also need to configure your app to send email correctly.
One way to do this is by using a 3-rd party SMTP server such as GMail or Zoho.
GMail requires a paid Google Apps for Business suite for use with a custom domain, while Zoho is free.
Once you configure your domain with Zoho, here is the email configuration
you can put in config/environments/production.rb:
config.action_mailer.default_url_options = { host: ENV['APP_HOST'], protocol: 'https' }
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.zoho.com',
port: 587,
domain: ENV['APP_HOST'],
user_name: ENV['SMTP_USER'],
password: ENV['SMTP_PASSWORD'],
authentication: :plain,
enable_starttls_auto: true,
}You also need to set Thredded.email_from in the Thredded initializer,
and config.mailer_sender in the devise initializer.
Then, set SMTP_USER/PASSWORD under app_user_env in vars/${APP}-prod and deploy.
NB: Many VPS hosting providers, such as Scaleway and DigitalOcean, block outgoing connections to 587 by default. Make sure to change this in the settings. For Scaleway, see this FAQ entry.
Zoho free tier is limited to 1,000 emails sent per day (up to 5,000 on paid plans), while GMail is limited to 2,000 / day, so you will want to use a specialized service like Mailgun, or host your own server once you exceed that. TODO: Postfix role.
Run the deployment playbook:
ansible-playbook deploy.yml -i hosts/${APP}-prod -e "config=vars/${APP}-prod.yml"Update the vars/$APP.yml file and either do a full deploy, or run:
ansible-playbook deploy.yml -e "config=vars/${APP}.yml" --tags envThe command above will only update the environment and then restart the app.
NB: Removing an environment variable from vars/$APP.yml does not
remove it from the environment file on the server (~$APP/.pam_environment).
The application and webserver logs are located at /var/www/$APP/shared/log.
The app service output (such as errors when starting the services)
is logged to /var/log/syslog.
In production:
# Replace "${IP}" with the server's IP address
ssh ${APP}@${IP} -t "cd /var/www/$APP/current && bundle exec rails c"To print a server IP address / hostname:
ansible 'webservers[0]' -i hosts/${APP}-prod -m debug -a 'var=ansible_host' | grep -oE "[0-9.]{2,}"On VirtualBox:
ssh $APP@127.0.0.1 -p 2222 -t "cd /var/www/$APP/current && bundle exec rails c"You can ssh into the VirtualBox instance on port 2222:
# As the "ubuntu" user (this user can run sudo without a password)
ssh ubuntu@127.0.0.1 -p2222 -i .vagrant/machines/vm1/virtualbox/private_key
# As the app user
ssh $APP@127.0.0.1 -p2222To run a command as the web user via Ansible:
# Use webservers[0] for just one of the servers
ansible webservers -u $APP -a "chdir=/var/www/$APP/current bundle exec rails db:seed"To see the output of commands as they run,
pass the -vvv flag to ansible or ansible-playbook.
- The Rails deployment role is based on: https://github.com/nicolai86/ansible-rails-deployment.
- Rbenv installation is based on: https://github.com/erasme/ansible-rbenv.
- The Nginx role is based on: https://github.com/jdauphant/ansible-role-nginx.
- The Memcached role is based on: https://github.com/geerlingguy/ansible-role-memcached.
- The Nginx HTTPS config is based on: http://stackoverflow.com/a/41948807/181228
- Let's encrypt HTTPS certs and auto-renewal are based on: