06 Oct 2017

From zero to rails-app-deployed on Linode:


  • Dump old database with mysqldump
  • Check which sites are even running by inspecting /etc/apache2/sites-enabled
  • Run du --max-depth=2 -b | sort -rn | less to see what's on the disk
  • Have a look in /home
  • Tar up everything interesting, and copy it to my laptop with scp
  • Delete large and uninteresting things to free up disk, then resize the disk image in the Linode manager so that I can create a new one without deleting the old one yet.
  • Basic setup

  • In Linode's manager, Deploy image. We'll use Ubuntu 17 rather than 16 LTS because this is more of a 'playground' machine than a production host.
  • Generate strong root password using Keepass, and save it in Keepass.
  • Rename the disks and profiles to make everything clear
  • Boot the machine
  • Secure access

  • ssh in as root@ . Edit .ssh/known_hosts to make this possible.
  • Meanwhile, scp .ssh/
  • On the server, mkdir .ssh && mv .ssh/authorized_keys && chmod -R go-rwx .ssh.
  • ssh - now the password isn't needed and the certificate is used
  • edit /etc/ssh/ssh_config and set PasswordAuthentication to no to prevent ssh bruteforce attacks.
  • edit /etc/hostname and /etc/hosts to set hostname
  • Install mysql and load database file

  • apt-get update
  • apt-get install mysql-server mysql-client default-libmysqlclient-dev
  • (without the last one, you can't install the mysql gem later, because you're missing the development files)
  • cat d.sql.gz | gunzip | mysql -u root -p
  • Prepare user for the website app

  • useradd gwynmorf && mkdir /home/gwynmorf && cp -R ~/.ssh /home/gwynmorf && chown -R gwynmorf /home/gwynmorf
  • Install nginx and passenger

  • apt-get install nginx
  • apt-get install -y dirmngr gnupg
  • apt-key adv --keyserver hkp:// --recv-keys 561F9B9CAC40B2F7
  • sh -c 'echo deb zesty main > /etc/apt/sources.list.d/passenger.list'
  • apt-get update
  • apt-get install -y libnginx-mod-http-passenger
  • Install ruby, node, npm

  • apt-get install ruby ruby-dev
  • (this will also install rubygems, but not bundler)
  • gem install bundler
  • we can't install node from apt - we get a very old version (4.7)
  • curl -sL | sudo -E bash -
  • apt-get install -y nodejs
  • apt-get install -y build-essential
  • npm install npm@latest -g
  • curl -sS | sudo apt-key add -
  • echo "deb stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update && sudo apt-get install yarn
  • Setup firewall

  • ufw status
  • ufw allow ssh/tcp
  • ufw logging on
  • ufw allow 'Nginx Full'
  • ufw enable
  • Deploy application

  • apt-get install git
  • Create ~/everpublish/shared/.env and ~/everpublish/shared/keys/firebase.json
  • gem install bundler
  • On laptop, bundle exec cap production deploy
  • On server, edit /etc/nginx/sites-enabled/
  • .

    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        root /home/gwynmorf/everpublish/current/public;
        passenger_enabled on;
            rails_env production;
            passenger_user gwynmorf;
    • service nginx restart

    Setup SSL

  • Copy certs back from laptop to /var/ssl

  • Add lines to sites-enabled/

    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ssl_certificate /var/ssl/;
    ssl_certificate_key /var/ssl/;
  • Execute /var/ssl/ just to check that it works.

  • .

    mkdir -p /home/gwynmorf/everpublish/current/public/.well-known/acme-challenge
    chmod -R 777 /home/gwynmorf/everpublish/current/public/.well-known
    openssl req -new -sha256 -key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\,")) >
    python --account-key letsencrypt-account.key --csr ./  --acme-dir /home/gwynmorf/everpublish/current/public/.well-known/acme-challenge >
    cat intermediate.pem >
    • Put in /etc/cron.monthly:


      cd /var/ssl ./

    • Force SSL on all connections by editing


    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        return 301$request_uri;
    server {
        listen 443 ssl http2;
       listen [::]:443 ssl http2;

    Setup DB backups to s3

  • wget && unzip
  • apt-get install python-dateutil
  • apt-get install python-setuptools
  • python install
  • create /etc/cron.daily/


    mysqldump -u root --password=somepassword --all-databases | gzip > /var/db_backups/date +%C%y%m%d-%H-%M.sql.gz s3cmd sync /var/db_backups/ s3://gwynmorfey-backup

  • Now we need to set up a user on s3 for the backups. We want to create an "IAM" user, not the "root" user (the email/password that controls the entire account)

    Start here:

    User name: dbbackup Access type: programmatic (so it will provide an access key and secret key)

    Attach user to new group with policy amazons3fullaccess

    Now we need to restrict it further but let's check it works first.

    After Review you'll get the access key id and secret access key

    • s3cmd --configure

    Accept defaults and test

    Lock down S3 access to just the right bucket

  • Go to the dbbackupgroup group:
  • Create an Inline Policy:
  • .

      "Version": "2012-10-17",
      "Statement": [
          "Effect": "Allow",
          "Action": [
          "Resource": "arn:aws:s3:::*"
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": ["arn:aws:s3:::gwynmorfey-backup"]
          "Effect": "Allow",
          "Action": [
          "Resource": ["arn:aws:s3:::gwynmorfey-backup/*"]
    • Detach the Managed Policy.