Heroku Buildpack
At the end of 2014 a first draft of a Heroku buildpack for crystal was createad. This was truly great. Being able to run crystal apps in the Heroku stack was charm idea.
While we continued to develop the language, the tools, and the standard library, the community around Crystal grew. Many were interested in trying their Crystal-powered web apps in Heroku. The network graph at github for the buildpack is quite big, especially for just a bunch of bash scripts!
However, due to some flaws in the approach, the buildpack failed to stay up to date with the latest versions of Crystal; but that is now solved! Yay!
While efforts to develop web frameworks continue nowadays, we wanted to share the very basic steps to use the Crystal buildpack to deploy a web application in Heroku without the need for any additional dependencies.
Create a Crystal project
This assumes you already have crystal installed.
Use crystal init app
to create the app.
$ crystal init app heroku101 create heroku101/.gitignore create heroku101/LICENSE create heroku101/README.md create heroku101/.travis.yml create heroku101/shard.yml create heroku101/src/heroku101.cr create heroku101/src/heroku101/version.cr create heroku101/spec/spec_helper.cr create heroku101/spec/heroku101_spec.cr Initialized empty Git repository in /Users/bcardiff/Work/Manas/crystal/heroku101/.git/ $ cd heroku101/
Note: During the rest of the post all the commands are executed from the heroku101/
directory.
The shard.yml
file declares the name of the project as heroku101
. This will be used by the buildpack to determine the main source file to compile: ./src/heroku101.cr
.
$ cat shard.yml name: heroku101 version: 0.1.0 ...
To create a simple http server edit the src/heroku101.cr
file and add the following content:
# file: src/heroku101.cr
require "http/server"
bind = "0.0.0.0"
port = 8080
server = HTTP::Server.new(bind, port) do |context|
context.response.content_type = "text/plain"
context.response << "Hello world, got #{context.request.path}"
end
puts "Listening on http://#{bind}:#{port}"
server.listen
To build and run the program:
$ crystal src/heroku101.cr Listening on http://0.0.0.0:8080
Open your browser at http://0.0.0.0:8080.
To stop the server just terminate the process by pressing Ctrl+C
.
Herokufy it
Right now the project knows nothing about Heroku. To get started, a Heroku application needs first to be registered. The easiest way to do this is via the Heroku toolbelt :
$ heroku create --buildpack https://github.com/crystal-lang/heroku-buildpack-crystal.git Creating app... done, ⬢ sleepy-thicket-16179 Setting buildpack to https://github.com/crystal-lang/heroku-buildpack-crystal.git... done https://sleepy-thicket-16179.herokuapp.com/ | https://git.heroku.com/sleepy-thicket-16179.git
The above command will generate a random app name. Check the docs to give your app a name from the beginning.
Before deploying, we need to make a small change. Heroku randomly assigns a port number to be used by the app. Thanks to the buildpack, this will be informed in a --port
option when running the application.
So, add a require "option_parser"
at the beginning of src/heroku101.cr
and override the port
variable default with:
OptionParser.parse! do |opts|
opts.on("-p PORT", "--port PORT", "define port to run server") do |opt|
port = opt.to_i
end
end
The full src/heroku101.cr
should be:
# file: src/heroku101.cr
require "http/server"
require "option_parser"
bind = "0.0.0.0"
port = 8080
OptionParser.parse! do |opts|
opts.on("-p PORT", "--port PORT", "define port to run server") do |opt|
port = opt.to_i
end
end
server = HTTP::Server.new(bind, port) do |context|
context.response.content_type = "text/plain"
context.response << "Hello world, got #{context.request.path}"
end
puts "Listening on http://#{bind}:#{port}"
server.listen
To build and run with --port
option:
$ crystal src/heroku101.cr -- --port 9090 Listening on http://0.0.0.0:9090
Or build an optimised release locally and execute it via:
$ crystal build src/heroku101.cr --release $ ./heroku101 Listening on http://0.0.0.0:8080 ^C $ ./heroku101 --port 9090 Listening on http://0.0.0.0:9090 ^C
Deploy!
When you are ready to go live with your app just deploy it the usual way with git push heroku master
.
$ git push heroku master Counting objects: 22, done. Delta compression using up to 8 threads. Compressing objects: 100% (17/17), done. Writing objects: 100% (22/22), 2.85 KiB | 0 bytes/s, done. Total 22 (delta 3), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Fetching set buildpack https://github.com/crystal-lang/heroku-buildpack-crystal.git... done remote: -----> Crystal app detected remote: -----> Installing Crystal (0.17.3 due to latest release at https://github.com/crystal-lang/crystal) remote: -----> Installing Dependencies remote: -----> Compiling src/heroku101.cr (auto-detected from shard.yml) remote: remote: -----> Discovering process types remote: Procfile declares types -> (none) remote: Default types for buildpack -> web remote: remote: -----> Compressing... remote: Done: 289.4K remote: -----> Launching... remote: Released v3 remote: https://sleepy-thicket-16179.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy.... done. To https://git.heroku.com/sleepy-thicket-16179.git * [new branch] master -> master
The buildpack will:
- Install the latest crystal release.
- Install project dependencies via shards.
- Compile the main source file in release mode.
- Run the web server process with
--port
option.
Specify the crystal version
If you want to use a different Crystal version, create a .crystal-version
file with the desired version, following crenv’s convention.
$ echo '0.17.1' > .crystal-version
Commit the changes in .crystal-version
and deploy.
$ git push heroku master Counting objects: 3, done. Delta compression using up to 8 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 301 bytes | 0 bytes/s, done. Total 3 (delta 1), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Fetching set buildpack https://github.com/crystal-lang/heroku-buildpack-crystal.git... done remote: -----> Crystal app detected remote: -----> Installing Crystal (0.17.1 due to .crystal-version file) remote: -----> Installing Dependencies remote: -----> Compiling src/heroku101.cr (auto-detected from shard.yml) ...
You will now notice the (0.17.1 due to .crystal-version file)
legend.
Whenever you are ready to upgrade to the latest crystal version, update the content of the file or just remove it and deploy again.
Show me the code!
Find all the sample source code used at https://github.com/bcardiff/sample-crystal-heroku101.
To contribute to crystal buildpack, just fork it. Contributions are welcome!