Rails Deployment for Open Jobs on Heroku

Setting targets for MVP workflow on open jobs

ยท

7 min read

MVP infra requirements

  • it runs
  • it is easy to deploy
  • it is fast to rollback
  • we can automate deployments
  • we can attach a CI pipeline to it easily

Github + Heroku will get this done.

Heroku is my go to for getting started on most rails project. It supports all the main workflows you might need: direct deploy with, git sync to repo, docker container deploy.

Again, here going to exploit again existing knowledge and preference of Heroku over Elasticbeanstalk from AWS.

Heroku is slower and harder to scale past a certain point. Good problem to have if we hit that point. For now it will get us started.

Basic Index Page

Before we deploy anything we need something to show. We will add a index route and a welcome message.

File: app/views/application/index.html.erb
1: <h1>Welcome to OpenJobs</h1>
2: <p>Repo can be found here: https://github.com/TristanToye/open-jobs</p>

Now there will be something to look at when deployed ๐Ÿ‘

Heroku Setup

Setup new app, connect to github repo.

Add postgres addon to Heroku app, I am lazy and usually use the UI for all of this not the CLI.

Here though we are going to add an app.json for the template aspect of the project

Docs here

File: app.json
01: {
02:   "name": "Open Jobs",
03:   "description": "Opens source brand-able job boards & recruiting portals.",
04:   "keywords": [
05:     "rails",
06:     "job search",
07:     "recruiting"
08:   ],
09:   "repository": "https://github.com/TristanToye/open-jobs",
10:   "scripts": {
11:     "postdeploy": "bundle exec rails db:seed && bundle exec rails db:migrate"
12:   },
13:   "env": {
14:     "RAILS_MASTER_KEY": {
15:       "description": "Rails encryption for config/credentials.yml.enc",
16:       "value": "REPLACE_ME"
17:     }
18:   },
19:   "formation": {
20:     "web": {
21:       "quantity": 1,
22:       "size": "free"
23:     }
24:   },
25:   "image": "heroku/ruby",
26:   "addons": [
27:     "heroku-postgresql:hobby-dev"
28:   ],
29:   "buildpacks": [
30:     {
31:       "url": "heroku/ruby"
32:     }
33:   ],
34:   "environments": {
35:     "test": {
36:       "scripts": {
37:         "test": "bundle exec rails test"
38:       }
39:     }
40:   }
41: }

Add a Procfile to repo for web server and release step

release: bundle exec rails db:migrate
web: bundle exec puma -C config/puma.rb

Add linux requirements to gemfile to run on heroku image

bundle lock --add-platform x86_64-linux

Update Readme with deploy btn

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/TristanToye/open-jobs)

And then in the spirit of eating our own dog food we will deploy with the btn we added.

Great it is now deployed on heroku! Can be found here open-jobs.herokuapp.com

Next up we want some CI in place to run on each push/merge to main branch.

CI options

We could easily use one of the solutions I have worked with before

  • Github actions
  • CircleCI
  • Heroku test pipeline

We already have gtihub and heroku in the mix so those make the most sense. Github actions are also free for open source, so this seems to make the most sense and keeps us out of any other platform unless we need it.

I also like trying new things and github actions does add a huge amount of freedom as to what we need to run the platform, and the community off the shelf actions are a huge win. For others looking to fork this adding the CI as part of the repo also gives them a bigger benefit with lower overhead.

Finally, this pushes us towards using docker as part of our workflow and means we likely will maintain more deployable code overall.

Github actions it is.

The prompt on github indicates we might want to start with a basic workflow found here github.com/actions/starter-workflows/blob/7..

Seems simple enough lets use it.

...As expected this does not work for our needs out of the box.

I think we can start with something even more simple to get a ruby image running rails for us.

A more realistic option of run ruby & postgres in github acitons can be found in a great guide here medium.com/@OwenTran/github-workflow-for-ra..

I ended up with for a starting point

File: .github/workflows/ruby-tests.yml
01: # This workflow uses actions that are not certified by GitHub.
02: # They are provided by a third-party and are governed by
03: # separate terms of service, privacy policy, and support
04: # documentation.
05: # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
06: # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
07: 
08: name: Ruby
09: 
10: on:
11:   push:
12:     branches: [ main ]
13:   pull_request:
14:     branches: [ main ]
15: 
16: jobs:
17:   build:
18: 
19:     runs-on: ubuntu-latest
20: 
21:     services:
22:       db:
23:         image: postgres:12
24:         ports:
25:           - 5432:5432
26:         env:
27:           POSTGRES_PASSWORD: password
28:           POSTGRES_USER: postgres
29: 
30:     steps:
31:     - uses: actions/checkout@v2
32: 
33:     - name: Install library for postgres
34:       run: sudo apt-get install libpq-dev
35: 
36:     - name: Set up Ruby
37:     # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
38:     # change this to (see https://github.com/ruby/setup-ruby#versioning):
39:       uses: ruby/setup-ruby@v1
40:       with:
41:         ruby-version: 2.7
42:         bundler-cache: true # runs 'bundle install' and caches installed gems automatically
43: 
44:     - name: Setup Database
45:       run: |
46:         bundle exec rails db:setup
47:       env:
48:         DB_PASSWORD: password
49:         DB_USERNAME: postgres
50:         RAILS_ENV: test
51: 
52:     - name: Run Tests
53:       env:
54:         DB_PASSWORD: password
55:         DB_USERNAM: postgres
56:         RAILS_ENV: test
57:       run: |
58:         # bundle exec rake test
59:         bundle exec rake --tasks
60:

And updated my database.yml to match for ENVs being used

File: config/database.yml
01: # PostgreSQL. Versions 9.3 and up are supported.
02: #
03: # Install the pg driver:
04: #   gem install pg
05: # On macOS with Homebrew:
06: #   gem install pg -- --with-pg-config=/usr/local/bin/pg_config
07: # On macOS with MacPorts:
08: #   gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
09: # On Windows:
10: #   gem install pg
11: #       Choose the win32 build.
12: #       Install PostgreSQL and put its /bin directory on your path.
13: #
14: # Configure Using Gemfile
15: # gem 'pg'
16: #
17: default: &default
18:   adapter: postgresql
19:   encoding: unicode
20:   # For details on connection pooling, see Rails configuration guide
21:   # https://guides.rubyonrails.org/configuring.html#database-pooling
22:   pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
23:   host: localhost
24:   sslmode: prefer
25: 
26: development:
27:   <<: *default
28:   database: open_jobs_development
29: 
30:   # The specified database role being used to connect to postgres.
31:   # To create additional roles in postgres see `$ createuser --help`.
32:   # When left blank, postgres will use the default role. This is
33:   # the same name as the operating system user running Rails.
34:   #username: open_jobs
35: 
36:   # The password associated with the postgres role (username).
37:   #password:
38: 
39:   # Connect on a TCP socket. Omitted by default since the client uses a
40:   # domain socket that doesn't need configuration. Windows does not have
41:   # domain sockets, so uncomment these lines.
42:   #host: localhost
43: 
44:   # The TCP port the server listens on. Defaults to 5432.
45:   # If your server runs on a different port number, change accordingly.
46:   #port: 5432
47: 
48:   # Schema search path. The server defaults to $user,public
49:   #schema_search_path: myapp,sharedapp,public
50: 
51:   # Minimum log levels, in increasing order:
52:   #   debug5, debug4, debug3, debug2, debug1,
53:   #   log, notice, warning, error, fatal, and panic
54:   # Defaults to warning.
55:   #min_messages: notice
56: 
57: # Warning: The database defined as "test" will be erased and
58: # re-generated from your development database when you run "rake".
59: # Do not set this db to the same as development or production.
60: test:
61:   <<: *default
62:   database: open_jobs_test
63:   username: <%= ENV['DB_USERNAME'] %>
64:   password: <%= ENV['DB_PASSWORD'] %>
65: 
66: # As with config/credentials.yml, you never want to store sensitive information,
67: # like your database password, in your source code. If your source code is
68: # ever seen by anyone, they now have access to your database.
69: #
70: # Instead, provide the password or a full connection URL as an environment
71: # variable when you boot the app. For example:
72: #
73: #   DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
74: #
75: # If the connection URL is provided in the special DATABASE_URL environment
76: # variable, Rails will automatically merge its configuration values on top of
77: # the values provided in this file. Alternatively, you can specify a connection
78: # URL environment variable explicitly:
79: #
80: #   production:
81: #     url: <%= ENV['MY_APP_DATABASE_URL'] %>
82: #
83: # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
84: # for a full overview on how database connection configuration can be specified.
85: #
86: production:
87:   <<: *default
88:   database: open_jobs_production
89:   username: open_jobs
90:   password: <%= ENV['OPEN_JOBS_DATABASE_PASSWORD'] %>
91:   sslmode: require

Now if you have been following you will know, we don't actually have anything to test here, s what are do doing? Good point.

We will start with a basic command and update from their.

ย