Finally Moving to Heroku

Heroku

This morning, I realized it was about time to really shop around and see if Heroku or AWS was the right choice for my projects. Now I've heard, and mentioned, that it's likely that the best solution is Heroku, and after digging into all the capabilities and costs, it really is far easier and cheaper to use Heroku and just let it be done. The support for Clojure is first class, and the tools have gotten a lot better, and the pricing has come down, and that was all it took.

The first thing to do was to sign up at Heroku for an account, and then set up that account with the right SSH key, and 2FA with Authy on my iPhone, and then put in the credit card info to be a Validated user - and get more free hours a month for the "Free" dynes. All this was very nice, and really smooth. I liked that the 2FA with Authy is as easy as it is, because it pulls in the account information and icon, and it just plain works.

The next thing was to walk through the example Clojure project to see how it's done. What really matters is that it works, and then I can pick out things from that project - which also uses Jetty, and pull them into my project to get it going as well. As expected, that went very well, and the documentation is really first rate. Just what I've heard I could expect.

Finally, I followed their instructions to scale down the dynos to 0 on the example project, and started to work on converting my project from the old deployment model I had to using Heroku. First off, we needed to pull in the Procfile and app.json from the example project, and then update them to match what we needed for this project. The Procfile was updated to be:

  web: java $JVM_OPTS -cp target/timeless-standalone.jar timeless.main web

because the old init.d scripts I'm used to writing had this same basic line in them. We have to fix up the Uberjar file name, but we'll do that in a minute, but this will launch the Jetty server on the right port, and have everything cooking just fine.

The app.json was updated to be:

{
  "name": "Timeless: Sequencing timestamps",
  "description": "A service/app that helps sequence timestamps from log files.",
  "image": "heroku/clojure"
}

and here, it's pretty obvious what to update - just the name and description.

At this point, we can create the application at Heroku:

  $ heroku create

We need to make a few changes to the code so that it would work with the Heroku infrastructure. First, we had to update the project.clj to add these entries:

  :min-lein-version "2.7.1"
  :plugins [[environ/environ.lein "0.3.1"]]
  :hooks [environ.leiningen.hooks]
  :dependencies [[org.clojure/clojure "1.10.0"]
                 ;; ...other dependencies...
 
                 ;; Heroku environment tools
                 [environ "1.0.0"]]
  :uberjar-name "timeless-standalone.jar"
  :profiles {:uberjar {:main timeless.main, :aot :all}}

Then, in main.clj, we needed to pull the port for Jetty from Heroku's environment variables, and to do this, we updated the code to read:

(defn handle-args
  "Function to parse the arguments to the main entry point of this project and
  do what it's asking. By the time we return, it's all done and over."
  [args app]
  (let [[params [action]] (cli args
             ["-p" "--port"     "Listen on this port" :default 8080 :parse-fn #(Integer. %)]
             ["-s" "--ssl-port" "Listen on this port" :default 8443 :parse-fn #(Integer. %)]
             ["-v" "--verbose" :flag true])
        quiet? (:quiet params)
        fixed? (:fixed params)
        port (Integer. (or (env :port) (:port params) 5000))]
    (cond
      (= "web" action)
		      (try
		        (jt/run-jetty app {:port port})
		        ; finally, close down the async jobs
		        (finally
		          ))
      :else
        (do
          (info "Welcome to Timeless!")
          (println "Welcome to Timeless")))))

the key being to check the :port value with the env function, and then put that in the or before the default from the CLI args. It makes sense, they will want to set the port on the process so that they can stack a lot of them on a single box.

After this, it's as simple as deploying the app with:

  $ git push heroku master
  $ heroku ps:scale web=1

and it should all be running just fine.

When I added the CNAME records to my DNS for this domain, I get a certificate problem because we're moving from bobbeaty.com to herokuapp.com - so I still have to figure that out to make things really smooth, but this is a huge step in the right direction.

UPDATE: the key is not to add it as a CNAME - but a simply web permanent redirection. If we do that, then we can add it as: bobbeaty.com/timeless and we're all good!