We have been migrating most of our projects to run on Docker containers for a while now, and since Crystal is our new favourite language ever since its first commit, running Crystal on Docker was a natural consequence as soon as we started using it in production.
The Crystal team at Manas keeps a debian-based image in Docker Hub for every new release of the language, and extending from the image is incredibly easy. Assuming your project has a shards.yml
file, as every Crystal project should, creating an image for your project is as easy as the following:
Similar as you would do in a Ruby app, we first add the definition of the dependencies (i.e. the shard
files) and install them, so any subsequent changes on the app itself that do not impact on the dependencies can reuse the Docker build cache. The next and final step is to compile the project <%= bold "in release mode"%>, and set it as the default command for the image.
As a base .dockerignore
file you could use:
doc/
libs/
.crystal/
.shards/
myproject
Alternatively, if you want to keep your Docker image sizes as small as possible, you can use the aforementioned approach to build your Crystal project, then copy the binary out of the image, and simply add it to a plain debian image. This way you are not shipping your code and the Crystal compiler along with your application.
A word of caution if you run your project on Docker Cloud: Crystal executables are blazingly fast, and they typically start up before Docker Cloud’s discovery services, which means that if your app container depends on a database
container, it could happen that the app will not correctly resolve the database
name as soon as it starts. Although your application should gracefully handle connection failures and retry after a reasonable timeout, an awful yet effective workaround for this issue is to just change the container CMD
to add a sleep
statement before actually starting the app.
We have been successfully running several Crystal apps in Docker with this approach, and it works like a charm, both setting them up in auto-builds or running them from a CI such as CircleCI. Let us know what your experience is if you try it out!