Using Kamal to Deploy a Ruby on Rails Project to AWS Lightsail
I learned Ruby on Rails in CSCE431 at Texas A&M University, and ever since then I’ve been learning a lot more of the workings of a web application, as well as the many strategies to manage it.
Usually for deployment, I use Heroku, which is fine. However, ever since I started initializing Ruby on Rails projects myself, however, I saw that it came pre-generated with a .kamal
folder, which I thought was interesting - just another one of the countless folders Ruby generates, I thought. However, I soon Googled around and realized that Kamal makes deployments easier through Docker. When it came time to deploy my CSCE483 senior capstone project, I thought it might be time to try it out. Reading the docs, I was honestly astounded by how simple it seemed, making me choose it over Heroku, which I’ve used countless times at this point.
As expected, it was not as easy as it appeared. My first issue is that my Rails secrets seemed to not be recognized - code blocks like Rails.application.credentials.google[:access_key]
seemed to throw errors saying the I was accessing a nil
variable. However, I soon realized that the fix is not that they weren't being decoded correctly, it's rather that in one step of the process, RAILS_MASTER_KEY
was just not set. After countless misdirections that led me to change the Docker app’s compiler settings, change the Ruby version I was using, I found that the fix was rather simple. I used the dig function to instead access these variables safely, which solved that issue. Now, even if these variables return nil
in an earlier step, the app runs safely, and I was able to confirm that when running, the secrets were indeed set correctly. Rails.application.credentials.google[:access_key]
transformed into Rails.application.credentials.dig(:google, :access_key)
. Furthermore, Kamal uses the latest committed code rather than the latest saved code on the file system.
Then, all I needed to do was setup AWS Route53 to point my domain to the Lightsail machine's IP address, and now I have encrypted traffic served from my server!
Then came the challenge of setting up CD - after struggling to manually get the GitHub Actions working, I found one that seemed to be similar and modified it to create my version for my needs. Here is the code:
name: CD | |
on: | |
push: | |
branches: | |
- main | |
jobs: | |
Deploy: | |
runs-on: ubuntu-latest | |
env: | |
DOCKER_BUILDKIT: 1 | |
RAILS_ENV: production | |
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} | |
KAMAL_REGISTRY_USERNAME: ${{ secrets.KAMAL_REGISTRY_USERNAME }} | |
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v3 | |
- name: Set up Ruby | |
uses: ruby/setup-ruby@v1 | |
with: | |
ruby-version: 3.4.1 # possible imrovement here | |
bundler-cache: true | |
- name: Install dependencies | |
run: | | |
gem install kamal | |
- name: Set Rails Master Key | |
env: | |
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} | |
run: echo "$RAILS_MASTER_KEY" > config/master.key | |
- name: Set Lightsail Key | |
env: | |
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} | |
run: echo "$SSH_PRIVATE_KEY" > sshKey.pem | |
- name: Set up Docker Buildx | |
id: buildx | |
uses: docker/setup-buildx-action@v2 | |
- name: Run deploy command | |
run: kamal setup |
It also made me think about the complexity that goes into Heroku when creating a new subdomain for every deployment and retiring the old ones seamlessly.
Overall, it was still a great experience moving away some of the curtains of abstraction that Heroku places to see a bit more of the moving parts.