I’ve written about hosting Middleman apps a couple of times before. This article supersedes the advice given in those earlier blog posts.

This new approach uses Heroku’s official Ruby buildpack and uses its asset precompile behaviour to build the Middleman site.

The advantage of this approach is that the site gets built on deploy and doesn’t need to be rebuilt every time that the app reboots. This should result in faster and more reliable app boots.


I used the latest version of Middleman (4.2.1) and Ruby (2.4.0) at the time of writing. You may need to adapt the steps if you’re using different versions…

  1. Create a new Middleman app/site:

    middleman init awesome-site
    

    If you’ve already got a Middleman app, you can skip this step…

  2. Append the following to the Gemfile:

    gem 'rake'
    gem 'puma'
    gem 'rack-contrib'
    

    I also recommend specifying the Ruby version by adding the following on a new line after source "https://rubygems.org"

    ruby "~> 2.4.0"
    

    If you use RVM or rbenv, also add a .ruby-version file with 2.4.0 as the contents)

  3. Run bundle install

  4. Add the following to the config.rb file:

    # Use “pretty” URLs (without the `.html` suffix)
    activate :directory_indexes
    
    # Append hashes to compiled assets
    activate :asset_hash
    

    You may also, optionally, wish to enable javascript and CSS minification

  5. Create a file called config.ru (replace its contents if it already exists) with the following contents:

    require "rack"
    require "rack/contrib/try_static"
    
    # Enable proper HEAD responses
    use Rack::Head
    
    # Add basic auth if configured
    if ENV["HTTP_USER"] && ENV["HTTP_PASSWORD"]
      use Rack::Auth::Basic, "Restricted Area" do |username, password|
        [username, password] == [ENV["HTTP_USER"], ENV["HTTP_PASSWORD"]]
      end
    end
    
    # Attempt to serve static HTML files
    use Rack::TryStatic,
        :root => "build",
        :urls => %w[/],
        :try => ['.html', 'index.html', '/index.html']
    
    # Serve a 404 page if all else fails
    run lambda { |env|
      [
        404,
        {
          "Content-Type" => "text/html",
          "Cache-Control" => "public, max-age=60"
        },
        File.open("build/404/index.html", File::RDONLY)
      ]
    }
    
  6. Add a Procfile to the project root:

    web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development}
    
  7. Add a new file named Rakefile:

    namespace :assets do
      task :precompile do
        sh "middleman build"
      end
    end
    
  8. Add a 404 page (source/404.html.erb):

    ---
    title: Page not found
    ---
    
    <h1>
      Page not found
    </h1>
    
  9. Commit your changes and push to Heroku.

Hurrah! That’s it.

Postscript

If you’re using an external asset pipeline that requires Node.js, you should add the Node.js buildpack to your Heroku app.

heroku buildpacks:set heroku/ruby
heroku buildpacks:add heroku/nodejs --index 1

Note: The Ruby buildpack should be last when you run heroku buildpacks

More about Heroku buildpacks: