<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://schepman.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://schepman.org/" rel="alternate" type="text/html" /><updated>2026-03-08T20:45:57+00:00</updated><id>https://schepman.org/feed.xml</id><title type="html">schepman.org</title><subtitle>You have found yourself to the personal website of Chris Schepman. What a treat. 🌝</subtitle><entry><title type="html">Putting Rails into production with Ansible</title><link href="https://schepman.org/2020/07/30/putting-rails-into-production-with-ansible" rel="alternate" type="text/html" title="Putting Rails into production with Ansible" /><published>2020-07-30T00:00:00+00:00</published><updated>2020-07-30T00:00:00+00:00</updated><id>https://schepman.org/2020/07/30/putting-rails-into-production-with-ansible</id><content type="html" xml:base="https://schepman.org/2020/07/30/putting-rails-into-production-with-ansible"><![CDATA[<p><em>Update: The Ansible setup described below has been retired, but the concepts still apply!</em></p>

<h1 id="tldr">TL;DR</h1>

<p>I went from a hard to reproduce, hand configured server for hosting a Rails app to a nicely automated setup using Ansible. The repo is <a href="https://github.com/cschep/ketten">here</a> and you can scope the README for easy to reproduce steps.</p>

<h1 id="the-long-version-aka-story-time">The long version AKA Story time</h1>

<p><strong>So you have this old Rails app</strong>. You know the one. It has been dutifully doing the right thing behind the scenes since 2014. Doesn’t really need any maintenance, but your friend’s business.. needs it around. Doesn’t depend on it, really, but like. It can’t go away.</p>

<p>You put it on a <a href="https://www.linode.com/?r=43da15f8d36adbbe4f26914646b1608a908abdb3">Linode</a> which you configured by hand and it’s been working just fine.</p>

<p>Fast forward, now it’s 2020. You’ve really been meaning to make an update or two. Maybe go from Rails 3 to 6? Where does the time go? How did I deploy this again?</p>

<p>It’s time to get serious. A quick DDG for “ansible rails” turns up a contender.</p>

<h1 id="it-works-until-it-doesnt">It works, until it doesn’t</h1>

<p>Major credit is due to <a href="https://github.com/EmailThis/ansible-rails">this</a> repo for getting me started. It doesn’t not work out of the box, which was weird, but I suppose you get what you pay for. <a href="https://github.com/noahgibbs/ansible-codefolio">This</a> fork was my savior in a time of great need though. This person fixed some of the errors that I was feeling frozen about. I tend to get stuck in this mode where if code is in a repo on github somewhere, then it must be right and I must be misunderstanding it. Nothing could be further from the truth! Thank you <code class="language-plaintext highlighter-rouge">github.com/noahgibbs</code> for the great reminder that there is a fork button for a reason, and that I needed to be the arbiter of my own destiny.</p>

<h1 id="configuration">Configuration</h1>

<p>I started by opening up <code class="language-plaintext highlighter-rouge">app-vars.yml</code> and getting to work. App name, github repo, etc. It’s all pretty straightforward. Something to keep in mind is that SSL/Certbot setup requires that you already have your DNS correctly pointed at whatever new machine you’re trying to deploy to. The certbot role will make real certificates. I tested using a subdomain at first, and then when it all worked I moved the real one over. Both had real certificates generated which is fine, but keep in mind Let’s Encrypt will rate limit you at some point. I never had a problem with it.</p>

<h1 id="you-can-tell-me-ill-put-it-in-the-vault">You can tell me. I’ll put it in the vault.</h1>

<p>Provisioning the machine went fine. I had to update a dependency to a newer version to make Ubuntu 20.04 happy, but that was no problem. I had a machine with a <code class="language-plaintext highlighter-rouge">deployer</code> user that had ssh and sudo configured, good to go.</p>

<p>I got a little tripped up by the fact that both Ansible and Rails each have a mechanism for encrypted secrets. Rails calls them credentials, Ansible, a vault.</p>

<p>Since I was updating an old Rails app I hadn’t encountered the new (circa 5.2) Rails credentials functionality and I wasn’t sure what to put where. There is now a <code class="language-plaintext highlighter-rouge">master.key</code> which is used to encrypt/decrypt. This gets created the first time you run <code class="language-plaintext highlighter-rouge">rails credentials:edit</code>. This file has a key in it that Rails uses to encrypt/decrypt sensitive information for you. It stores the encrypted information in a file called <code class="language-plaintext highlighter-rouge">credentials.yml.enc</code>. This credentials file will be in <code class="language-plaintext highlighter-rouge">config/</code> and potentially even <code class="language-plaintext highlighter-rouge">config/&lt;environment&gt;</code>. You can then check in the encrypted file which keeps it all in once place. Anyone with a key can view/edit. You DO NOT check the key into your repo, but share it directly with team using something like 1Password to keep it safe. Since we’re trying to automate our deployment, we need to get the contents of this file onto our server so when Rails boots up in production mode it can read the secret information and go on its merry way.</p>

<p>This is where Ansible’s vault comes in. It’s essentially the same mechanism, a key and an encrypted file you can share publicly. Using the ansible command we create a new vault e.g. <code class="language-plaintext highlighter-rouge">ansible-vault create group_vars/all/vault.yml</code> and we’re off to the races. Inside of this file we put our production postgres password, and our rails master key. I was confused at first and put the <code class="language-plaintext highlighter-rouge">secret_key_base</code> as the master key. Whoops! Provide the string here that is found in the file <code class="language-plaintext highlighter-rouge">master.key</code> so that Ansible can securely pass it along when running Rails in production.</p>

<p>You can use the <code class="language-plaintext highlighter-rouge">ansible-vault</code> command by specifying a key file in <code class="language-plaintext highlighter-rouge">ansible.cfg</code> or by passing the flag <code class="language-plaintext highlighter-rouge">--ask-vault-pass</code>. If you use a key file DO NOT check it into source control! Same as the Rails one. Secrets abound.</p>

<h1 id="two-for-the-price-of-two">Two for the price of two</h1>

<p>Why do we need these two mechanisms that are essentially doing the same thing and making your life more complicated? Great question! It’s really just a necessary evil to gain the automated delight that is having Ansible around.</p>

<p>Rails needs a way to have secrets. So far so good. Ansible needs to know the secrets, but since Ansible isn’t a trustworthy human we also have to protect ourselves from Ansible knowing the secrets.</p>

<p>I think it’s worth it, but it definitely confused me at first. Up to you!</p>

<h1 id="deploy">Deploy!</h1>

<p>Our deploy playbook uses <a href="https://ansistrano.com/">Ansistrano</a> to coordinate deploys. This is a pair of Ansible playbooks that mimic the functionality of <a href="https://capistranorb.com/">Capistrano</a> and do a good job of it. The name feels a little.. not perfect, but it works!</p>

<p>I didn’t have any idea how it would find these playbooks because I hadn’t installed them, but I was impressed it could figure it out. I was wrong to be impressed because it had no idea where they were. Naturally. The simple fix was to use <a href="https://galaxy.ansible.com/">Ansible Galaxy</a> to install them myself. I didn’t know Ansible Galaxy existed when I started this project so I actually got a bit stuck here!</p>

<p><code class="language-plaintext highlighter-rouge">$ ansible-galaxy install ansistrano.deploy ansistrano.rollback</code></p>

<p>Super easy, time to deploy!</p>

<h1 id="databaseyml">Database.yml</h1>

<p>I followed the instructions this far and started getting database access problems. My code was there, my database config was there, what’s going on?</p>

<p>I realized that the playbook was copying our production database config to the server, but not linking it to Rails. To fix this I added a symlink step to the <code class="language-plaintext highlighter-rouge">after_cleanup.yml</code> that Ansistrano runs after deploying.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- name: symlink database.yml
  file:
    src: "/config/database.yml"
    dest: "/config/database.yml"
    owner: ""
    group: ""
    state: link
    force: yes
</code></pre></div></div>

<p>Voila! The database is off and running.</p>

<h1 id="so-close">So close</h1>

<p>The final thing that tripped me up was that I had a <code class="language-plaintext highlighter-rouge">vendor</code> folder in my Rails root holding (you guessed it) vendored JavaScript. This problem probably doesn’t exist if you are using webpacker, but the deploy playbook by default was creating a shared vendor folder for gems, and then symlinking right over the top of my already existing vendor folder. An easy fix was to just call the folder something else. I am a creative force so naturally I chose the name: <code class="language-plaintext highlighter-rouge">gemvendor</code>.</p>

<h1 id="success">Success</h1>

<p>After all of this my app worked great and I was able to push up changes and re-run the deploy script just like I wanted. I could also destroy this box on Linode, grab a new one, and have a new server running my app in about 20 minutes. The initial build out takes a while because we build Ruby from scratch AND install some gems that have to build some C. Follow up deploys are fast.</p>

<p>I have updated the repo for my app with step by step instructions for using the updated Ansible playbooks that are contained in there. At some point I will perhaps split them into their own repo but for now I only use them in this one app so that is where they live.</p>

<p>Happy deploying!</p>]]></content><author><name>Chris Schepman</name></author><summary type="html"><![CDATA[Update: The Ansible setup described below has been retired, but the concepts still apply!]]></summary></entry><entry><title type="html">This site</title><link href="https://schepman.org/2020/07/29/this-site" rel="alternate" type="text/html" title="This site" /><published>2020-07-29T00:00:00+00:00</published><updated>2020-07-29T00:00:00+00:00</updated><id>https://schepman.org/2020/07/29/this-site</id><content type="html" xml:base="https://schepman.org/2020/07/29/this-site"><![CDATA[<p><em>Update: This site is now hosted on GitHub Pages. The Ansible/Linode setup described below has been retired, but the concepts still apply!</em></p>

<p>Alternative title: Hosting your own Jekyll blog with Ansible, Nginx, and Ubuntu 20.04 LTS</p>

<p>Not as fun though.</p>

<p>It seems fitting that my first post would be a meta post about how it all works. We must talk about our creations, yes? If a tree falls in a forest and no one is around to hear it, does it make a sound?</p>

<p>Let us observe as much as we can, I say! If for no other reason than to give the servers running our simulation a run for their money. Or credits, or whatever.</p>

<h2 id="tldr">TL;DR</h2>

<p>This is a static website generated with <a href="https://jekyllrb.com/">Jekyll</a>, hosted on a <a href="https://www.linode.com/?r=43da15f8d36adbbe4f26914646b1608a908abdb3">Linode</a>, running <a href="https://ubuntu.com/download/server">Ubuntu 20.04 LTS</a>. Provisioned and deployed with <a href="https://www.ansible.com/">Ansible</a>. Toss in an SSL cert from <a href="https://letsencrypt.org/">Let’s Encrypt</a>. Even a dash of <a href="https://goaccess.io/">GoAccess</a> to watch all of those delicious visitors in real time? Sold!</p>

<h2 id="why-in-which-i-justify-my-existence---meaning-is-created-not-an-absolute">Why? (In which I justify my existence - meaning is created! not an absolute!)</h2>

<p>To fend off the inevitable heat death of the universe with toil. With work. For a purpose beyond ourselves!</p>

<p>Nah, it’s just fun. I enjoy control and knowing how things work. Heroku and Netlify are cool options. There are many others. If you don’t care about doing this yourself I super don’t blame you. If you do, let’s dive in!</p>

<h2 id="why-actually-answering-the-question">Why! (Actually answering the question)</h2>

<p><strong>Linode:</strong> I have been using Linode for years. They aren’t Amazon, Google, or any other major nightmare of a company. I consider that a <em>Good Thing</em> on the whole. Though I am a citizen on planet earth, we’re all doing our best. I use them to host a few web things, including this site. A 5$ VPS hosting a static site can get pretty well hammered and be fine. If I ever write something that so many people care about that this thing falls down I will consider it 100% a victory only. I challenge you, dear readers, to make me eat these words.</p>

<p><strong>Jekyll:</strong> Hugo can generate sites insanely fast and I genuinely enjoy Go. That said.. I don’t know how you can get the templating system so wrong. I am fully willing to admit that I am just not smart enough to use it, but.. I fought Hugo for hours, and still didn’t get what I wanted. I switched to Jekyll and everything did what I expected almost instantly. Old dog new tricks? I dunno, man. Do what makes you happy … if you are lucky enough to have any idea what that is.</p>

<p><strong>NGINX:</strong> NGINX runs the internet. It runs my little piece of the internet as well. What else are you going to use.. Apache? Something new that people haven’t hammered on as much? Something that wasn’t written in C by a russian? Good luck with that.</p>

<p><strong>Ubuntu:</strong> Straightforward and stable. I’m young enough that I caught the Ubuntu train before the Debian one got to me. I used Arch for tinkering around, and thought it was very cool when I took three days to build out a Gentoo install on an old laptop. Servers need to just work. This seemed like the most boring choice. But like.. sexy boring.</p>

<p><strong>Let’s Encrypt:</strong> A non-profit that’s only goal is to make the web a safer place by providing free SSL certs. Sign me up. Have I missed any good reasons I should skeptical here? Let’s me just hop on twi.. NOPE. Happiness is fleeting and this one, I think I’ll keep this one for myself.</p>

<p><strong>GoAccess:</strong> We’ve got to stop giving all the data to Google only to have them treat us badly in return. Do no evil. Psh. I’m trying to de-google my life lately and it’s going.. ok. It turns out having a little self respect is harder than it looks. This beautiful (and free!) software parses the logs you’ve already got to give you pretty results in the terminal OR it will generate a nice (live updating!) HTML page. Also written by one guy in C. Patterns emerge? Seriously though. Needed a websocket server.. wrote a websocket server. May we all find our passions.</p>

<h2 id="how">How?</h2>

<p>Potentially this is the part that anyone might actually find helpful. Most of this work is based on the work already done <a href="https://github.com/EmailThis/ansible-rails">here</a>. There are some thing straight up broken there, so when I got stuck, I found inspiration in a fork <a href="https://github.com/noahgibbs/ansible-codefolio">here</a>. Standing on the shoulders of giants and all that!</p>

<h3 id="preprovision">Preprovision</h3>

<p>First and foremost! Make sure you check out <code class="language-plaintext highlighter-rouge">app-vars.yml</code> and set the variables there correctly. They are all set for my site (obviously) but should easily work for you.</p>

<p>Pay special attention to <code class="language-plaintext highlighter-rouge">nginx_https_enabled</code>. It defaults to <code class="language-plaintext highlighter-rouge">true</code> because I’m not messing around. You can turn it off if your domain isn’t quite ready to point to this machine, or like.. you hate privacy and seeing good prevail over evil.</p>

<p>Now you want to get a server from Linode. Or anywhere. It has to have your SSH key for the root user (Hey there Eagle Eye. Don’t get mad. We’ll get that disabled! Thanks for caring and know that I see you.).  Also an IP address. Put that IP address in your inventory file(s) and hold onto your butts. You could probably get away with password authentication somehow but then you’d be back to fighting for the wrong side. Don’t give in.</p>

<p>These steps are carried out through this <a href="https://github.com/cschep/schepman.org/blob/master/ansible/preprovision.yml">playbook</a>.</p>

<p>Run <code class="language-plaintext highlighter-rouge">ansible-playbook -i inventories/preprovision.ini preprovision.yml</code>.</p>

<p>This phase gets some basics going. Installs a user that isn’t root, get an ssh key in place for that user, etc. The reason we use a whole inventory file for this stage is that it sets the user to <code class="language-plaintext highlighter-rouge">root</code>. The production inventory uses the <code class="language-plaintext highlighter-rouge">deployer</code> user (or whatever you called it in <code class="language-plaintext highlighter-rouge">app-vars</code>!). You don’t need to have two separate inventory files for this, but it’s nice to not have to think about providing the correct user when running the playbooks.</p>

<h3 id="provision">Provision</h3>

<p>The meat and potatoes of our situation is handled here. You can reference this playbook <a href="https://github.com/cschep/schepman.org/blob/master/ansible/provision.yml">here</a>.</p>

<p>Run <code class="language-plaintext highlighter-rouge">ansible-playbook -i inventories/production.ini provision.yml</code>.</p>

<ul>
  <li>Harden SSH (Phew!)</li>
  <li>Install and configure NGINX</li>
  <li>Install and run certbot to get Let’s Encrypt certs</li>
  <li>Setup a cron job for certbot to refresh the certs</li>
  <li>Setup UFW to only allow ports 80, 443, and 22 (80 to helpfully redirect people to 443, 22 for ssh)</li>
  <li>Install goaccess (beautiful data!)</li>
</ul>

<h3 id="deploy">Deploy</h3>

<p>This <a href="https://github.com/cschep/schepman.org/blob/master/ansible/deploy.yml">playbook</a> is dead simple because deploying a static site is so easy.</p>

<p>All it does is make sure there is a folder in the right place, and then rsyncs our static site to it.</p>

<p>You have to remember to build the site yourself even. Maybe I should build that in. Alas.</p>

<p><code class="language-plaintext highlighter-rouge">JEKYLL_ENV=production bundle exec jekyll build</code></p>

<p><code class="language-plaintext highlighter-rouge">ansible-playbook -i inventories/production.ini deploy.yml</code></p>

<p>If all went well you should be able to surf on over to your domain and see your beautiful posts. Like this one.</p>

<p>If you’re into it, Check your <a href="https://developers.google.com/speed/pagespeed/insights/">google page speed</a>! Do an <a href="https://www.ssllabs.com/ssltest/">SSL labs test</a>!</p>

<p><img alt="google page speed results at 100%" src="/assets/images/google-page-speed-100.png" /></p>

<p><img alt="ssl test results at A+" src="/assets/images/ssl-test-a+.png" /></p>

<p>Bask in the glorious results!</p>

<h2 id="goaccess-bonus">GoAccess Bonus</h2>

<p>If you’re still here you get a fun bonus.</p>

<p>I was really excited to find out about <a href="https://goaccess.io">GoAccess</a> and while I’m no expert, I wanted to pass along a fun little setup to get the real time HTML going through an SSH tunnel.</p>

<p>I wasn’t super keen to share the logs of my blog publicly, also opening up another port in the firewall doesn’t overwhelm me with excitement.</p>

<p>What I do is fire up an SSH tunnel like so!</p>

<p><code class="language-plaintext highlighter-rouge">ssh -L 8000:localhost:8000 -L 7890:localhost:7890 deployer@192.81.130.222</code></p>

<p>For anyone not as familiar with SSH, this essentially means that when you access ports 8000, and 7980 on your personal machine, they will (through your secure SSH connection) access those ports on your server. So useful!</p>

<p>So, then on the remote machine I use <code class="language-plaintext highlighter-rouge">tmux</code> to run:</p>

<p><code class="language-plaintext highlighter-rouge">sudo goaccess /var/log/nginx/access.log -o report.html --log-format=COMBINED --real-time-html</code></p>

<p>This fires up <code class="language-plaintext highlighter-rouge">goaccess</code> pointed at our logs. Then <code class="language-plaintext highlighter-rouge">ctrl-b c</code> to make a new tmux tab and fire up a tiny webserver.</p>

<p><code class="language-plaintext highlighter-rouge">python3 -m http.server</code></p>

<p>This starts a webserver at port 8000 to share the html file that goaccess generates. You can use anything you want for this but python3 is there and it’s easy.</p>

<p>Go ahead and navigate to <code class="language-plaintext highlighter-rouge">http://localhost:8000/report.html</code> on your local machine and the realtime html pops up! Beauty.</p>

<p>Any web server will work, but trying to host it through SSL and opening ports gave me fits. The SSH tunnel really makes it nice.</p>

<p>Happy hosting to you all and thank you for reading. 👨🏼‍💻🎉</p>]]></content><author><name>Chris Schepman</name></author><summary type="html"><![CDATA[Update: This site is now hosted on GitHub Pages. The Ansible/Linode setup described below has been retired, but the concepts still apply!]]></summary></entry></feed>