A simple hello world app.
A bare minimum haskell application, with a library and executable component to test various development and deployment tools aimed at improving Developer Experience and Operational efficiency.
See specific sections below to learn more.
Unlike most mainstream languages, Haskell applications require very few dependencies from the target machine/host thanks to the RTS (RunTime System) being bundled with the built binary/executable.
One could argue docker is moot; i.e., the app itself is self-contained and uncaring of the host environment dependencies or the lack-thereof. However, we may still want containerization to achieve disposable computation environments. Ex: on a giant beefy central machine, launch an instance of a task or web-service and then tear it down when the need or traffic load subsides.
And docker-izing a Haskell application is rather beautiful, as the ENTRY POINT
is the executable.
Not all container images are equal. You loose the feeling of "light"/cheap instances when images push towards hundreds of MBs or a few GBs (interpreted or VM languages).
Haskell apps require very little from the host OS. So why not cut the chase and get as close to "metal" as possible with a barebones OS? We achieve some nice benefits:
- quick upload (of the whole build app image)
- quick cloud deployment
- reduced attack surface / principle of least privilege; i.e., there isn't really much else besides your app to compromise security
Learn more about Alpine.
Haskell applications are best compiled/built on the same architecture/OS as the deployment target. May feel like a hassle when you already have say stack
/cabal
already pre-configured, but it's worth it. The cost to set up a "dev" or "build" env from scratch dramatically goes down in the Docker way as you'd only require Docker
. In cases where your app requires system/foreign dependencies (ex: a C lib), the Docker way ensures you work with the exact same runtime from development to production.
-
Build and run
hello-world
(a pure haskell CLI app)$ docker-compose build hello-world # builds, tests, packages executable(s), and produces a tagged Docker image "hello-world:latest" $ docker-compose run hello-world someFunc
-
Build and run
hello-postgresql
(a haskell CLI app with C lib dependency)$ docker-compose build hello-postgresql # builds, tests, packages executable(s), and produces a tagged Docker image "hello-postgresql:latest" $ docker-compose up --build Creating hello-world_hello-world_1 ... done Creating hello-world_postgres_1 ... done Creating hello-world_hello-postgresql_1 ... done hello-postgresql_1 | dbFour hello-postgresql_1 | 4 hello-world_hello-postgresql_1 exited with code 0
-
Marvel at how compact app images are
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-postgresql latest 04198bd1418d 8 minutes ago 21.6MB hello-world latest 6f085d084f4a About an hour ago 7.36MB
- GHC version
- the version of
ghc
shipped with a stackage resolver MUST match the version ofghc
available on Alpine. - otherwise
stack
will try to buildghc
from source. This usually doesn't go so well. - the official alpine ghc version can be found here.
- the version of
.env
file can be used to store configurations that can then be consumed as ENV vars. Despite being so simple, it addresses config management in an elegant way when paired with Docker.
Docker-compose supports .env
out of the box. Your (dockerized) application can also access the same .env
vars by whitelisting/passing the env vars in docker-compose
. In addition, the same vars can be used to bootstrap other services like database credentials. This simply makes the env file the source of truth for configs.
Alternatively, if docker-compose is not desired/applicable, dotenv can be used to consume .env as ENV vars directly from your application.