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 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!