This is a simple example of a Haskell application running in a Docker container. The application is a Warp server that responds to every request with "Hello, world!". So, you know, not very exciting. But the entire thing can be built, configured, and run inside a Docker container.
Note that containerization does not prevent you from running this application like normal. In fact, assuming you already have Stack installed you can get it up and running with:
> stack setup
> stack build
> stack exec example
But we are all already familiar with that. Let's see how we can build this project with Docker instead!
Building the container starts from an empty Debian image, gets Stack, installs GHC, builds the dependencies, and finally installs the project. You can do all that with:
> docker build . --tag example
The --tag
is optional. If you don't include it, the resulting image will only
be identifiable by a hash. Giving it a tag makes it much easier to use.
The container will take a while to build. Once it's done, you can run the server with:
> docker run --interactive --publish-all --tty example
There are a few flags here, so this is what they do:
--interactive
keeps STDIN open so that you can kill the server with Ctrl-C.--publish-all
"publishes" the containers exposed ports to the host. This means that port 80 in the container will be avilable as a random port on the host.--tty
allocates a TTY so that STDOUT works.
Once the server is running, you can run figure out which port to connect to with:
> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1781a308c625 example "/bin/sh -c /usr/loca" 28 seconds ago Up 27 seconds 0.0.0.0:32773->80/tcp grave_bhabha
For that particular run, pointing your browser to http://localhost:32773
would show a page served by the application.
You should play around with changing the application (Main.hs
), the
dependencies (package.yaml
), and the resolver/compiler (stack.yaml
) to see
how they affect rebuilds.
Docker's caching works with layers; if you keep a lower layer the same, it will be reused. But any change to a layer causes a rebuild of that layer and everything after it. That means changing the resolver will cause the entire thing to be rebuilt from the ground up.
Note that this Docker container will take over two gigabytes of space on your machine. You can see how much space it use with:
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
debian 8.6 19134a8202e7 4 weeks ago 123.1 MB
example latest d1bbc77a84f9 14 minutes ago 2.158 GB
It is possible (and easy!) to make smaller images. This image is made to be
easy to understand and quick to develop on. Usually making smaller images means
making them less suitable for development. If this image were aiming to be as
small as possible, it would be at most a few megabytes larger than the base
image (debian:8.6
, which is about 123 MB).
Smaller base images are also available! Alpine Linux provides a base image that only takes up 5 MB!
Thank you very much for the gist! Your code was of great help when I was setting up a similar docker image on Windows. Here's my blog post about this: http://hack-your-fridge-or-die-trying.blogspot.ie/2017/09/docker-image-to-build-and-run-your.html