{"id":16339130,"url":"https://github.com/phadej/docker-haskell-example","last_synced_at":"2025-10-06T03:47:17.831Z","repository":{"id":65095216,"uuid":"195269751","full_name":"phadej/docker-haskell-example","owner":"phadej","description":"How to build Docker image for Haskell web app?","archived":false,"fork":false,"pushed_at":"2019-07-20T18:50:27.000Z","size":11,"stargazers_count":30,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-06T15:07:44.920Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dockerfile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phadej.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-04T16:00:36.000Z","updated_at":"2024-12-23T08:50:52.000Z","dependencies_parsed_at":"2022-12-31T06:58:08.467Z","dependency_job_id":null,"html_url":"https://github.com/phadej/docker-haskell-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/phadej/docker-haskell-example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fdocker-haskell-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fdocker-haskell-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fdocker-haskell-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fdocker-haskell-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phadej","download_url":"https://codeload.github.com/phadej/docker-haskell-example/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fdocker-haskell-example/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270850275,"owners_count":24656474,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T23:53:34.091Z","updated_at":"2025-10-06T03:47:12.794Z","avatar_url":"https://github.com/phadej.png","language":"Dockerfile","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\ntitle: Multi-stage docker build of Haskell webapp\nauthor: Oleg Grenrus\ntags: engineering\n---\n\nSince Docker 17.05, there is support for [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/).\nThis is an example and tutorial for using it to build simple Haskell webapp.\nThe setup is simple: single `Dockerfile`, yet the resulting docker image\nis only megabytes large.\n\nEssentially, I read through [Best practices for writing Dockerfiles](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)\nand made a `Dockerfile`.\n\n*A word of warning:* If you think Nix is the tool to use, I'm fine with that.\nBut there's nothing for you in this tutorial.\nThis is an opinionated setup: Ubuntu and `cabal-install`'s nix-style build.\nAlso all non-Haskell dependencies are assumed to be avaliable for install\nthrough `apt-get`. If that's not true in your case, maybe you should\ncheck Nix.\n\nThe files are on [GitHub: phadej/docker-haskell-example](https://github.com/phadej/docker-haskell-example).\nI refer to files by names, not paste them here.\n\nAssuming you have docker tools installed, there are seven steps to build a docker image:\n\n1. Write your application. Any web-app would do. The assumptions are that the app\n    - listens at port 8000\n    - doesn't daemonize itself\n\n   I use a minimal servant app:\n   [`docker-haskell-example.cabal`](https://github.com/phadej/docker-haskell-example/blob/master/docker-haskell-example.cabal) and\n   [`Main.hs`](https://github.com/phadej/docker-haskell-example/blob/master/src/Main.hs).\n   If you want to learn about servant, [its tutorial](https://haskell-servant.readthedocs.io/en/stable/tutorial/index.html) is a good starting point.\n\n2. Write `cabal.project` containing at least\n\n   ```plain\n   index-state: 2019-06-17T09:52:09Z\n   with-compiler: ghc-8.4.4\n   packages: .\n   ```\n\n   - Pinning `index-state` makes builds reproducible enough\n   - `with-compiler` select the compiler so it's not the default `ghc`\n\n3. Add [`.dockerignore`](https://github.com/phadej/docker-haskell-example/blob/master/.dockerignore).\n   The more stuff you can ignore, the better. Less things to copy to docker build context.\n   Less things would invalidate docker cache.\n   Especially hidden files are not hidden from docker, like editors' temporary files.\n   I hide `.git` directory. If you want to burn git-hash look at known issues section.\n\n4. Add [`Dockerfile`](https://github.com/phadej/docker-haskell-example/blob/master/Dockerfile)\n   and [`docker.cabal.config`](https://github.com/phadej/docker-haskell-example/blob/master/docker.cabal.config).\n   `docker.cabal.config` is used in `Dockerfile`.\n   In most cases you don't need to edit `Dockerfile`. You need, if you need some additional system dependencies.\n   The next step will tell, if you need something.\n\n5. Build an image with\n\n   ```bash\n   docker build --build-arg EXECUTABLE=docker-haskell-example --tag docker-haskell-example:latest .\n   ```\n\n   If it fails, due missing library, see next section. You'll need to edit\n   `Dockerfile`, and iterate until you get a successful build.\n\n6. After successful build, you can run the container locally\n\n   ```bash\n   docker run -ti --publish 8000:8000 docker-haskell-example:latest\n   ```\n\n   This step is important, to test that all runtime dependencies are there.\n\n7. And try it from another terminal\n\n    ```bash\n    curl -D - localhost:8000\n    ```\n\n    It should respond something like:\n\n    ```plain\n    HTTP/1.1 200 OK\n    Transfer-Encoding: chunked\n    Date: Thu, 04 Jul 2019 16:15:37 GMT\n    Server: Warp/3.2.27\n    Content-Type: application/json;charset=utf-8\n\n    [\"hello\",\"world\"]\n    ```\n\nWhat happens in the Dockerfile\n------------------------------\n\nThe `Dockerfile` is written with a monorepo setup in mind. In other words\nsetup, where you could build different docker images from a single repository.\nThat explains the `--build-arg EXECUTABLE=` in a `docker build` command.\nIt's also has some comments explaining *why* particular steps are done.\n\nThere are two stages in the `Dockerfile`, *builder* and *deployment*.\n\n\u003ch3\u003eBuilder stage\u003c/h3\u003e\n\nIn builder stage we install all **build dependencies**,\nseparating them into different `RUN`s, so we could avoid\ncache invalidation as much as possible.\nA general rule: *Often changing things have to installed latter*.\n\nWe install few dependencies from Ubuntu's package repositories.\nThat list is something you'll need to edit once in a while.\nThe assumption is that all non-Haskell stuff comes from there\n(or some PPA). There's also a corresponding list in *deployment* stage, there\nwe install only non-dev versions.\n\n```dockerfile\n# More dependencies, all the -dev libraries\n# - some basic collection of often needed libs\n# - also some dev tools\nRUN apt-get -yq --no-install-suggests --no-install-recommends install \\\n    build-essential \\\n    ca-certificates \\\n    curl \\\n    git \\\n    libgmp-dev \\\n    liblapack-dev \\\n    liblzma-dev \\\n    libpq-dev \\\n    libyaml-dev \\\n    netbase \\\n    openssh-client \\\n    pkg-config \\\n    zlib1g-dev\n```\n\nAt some point we reach a point, where we add `*.cabal` file.  This is something\nyou might need to edit as well, if you have multiple cabal files in different\ndirectories.\n\n```dockerfile\n# Add a .cabal file to build environment\n# - it's enough to build dependencies\nCOPY *.cabal cabal.project /build/\n```\n\nWe only add these, so we can build dependencies.\n\n\n\n```dockerfile\n# Build package dependencies first\n# - beware of https://github.com/haskell/cabal/issues/6106\nRUN cabal v2-build -v1 --dependencies-only all\n```\n\nand their cache\nwon't be violated by changes in the actual implementation of the webapp.\nThis is common idiom in `Dockerfile`s.\n[Issue 6106](https://github.com/haskell/cabal/issues/6106) might\nbe triggered if you vendor some dependencies. In that case\nchange the build command to\n\n```dockerfile\nRUN cabal v2-build -v1 --dependencies-only some-dependencies\n```\n\nlisting as many dependencies (e.g. `servant`, `warp`) as possible.\n\nAfter dependencies are built, the rest of the source files are added\nand the executables are built, stripped, and moved to known location\nout of `dist-newstyle` guts.\n\n\u003ch3\u003eDeployment image\u003c/h3\u003e\n\nThe deployment image is slick. We pay attention and don't install\ndevelopment dependencies anymore. In other words we install only **runtime dependencies**.\n E.g. we install `libgmp10`, not `libgmp-dev`.\nI also tend to install `curl` and some other cli tool to help debugging.\nIn deployment environments where you can shell into the running containers, it\nhelps if there's something you can do. That feature is useful to debug network\nproblems for example.\n\nThe resulting image is not the smallest possible,\nbut it's not huge either:\n\n```\nREPOSITORY               TAG      SIZE\ndocker-haskell-example   latest   137MB\n```\n\nCold build is slow.\nRebuilds are reasonably fast,\nif you don't touch `.cabal` or `cabal.project` files.\n\nKnown issues\n------------\n\n- If you have `data-files`, situation is tricky:\n  Consider using\n  [`file-embed-lzma`](https://hackage.haskell.org/package/file-embed-lzma)\n  or [`file-embed`](https://hackage.haskell.org/package/file-embed)\n  packages. I.e. avoid `data-files`.\n\n- Cabal issue [#6106](https://github.com/haskell/cabal/issues/6106)\n  may require you to edit `--dependencies-only` build step, as explained above.\n\n- Git Hash into built executable. My approach is to ignore whole `.git`\n  directory, as it might grow quite large.\n  Maybe unignoring (with `!`) of `.git/HEAD` and `.git/refs` (which are relatively small)\n  will make `gitrev` and alike work. Please tell me if you try!\n\n- Caching of Haskell dependencies is very rudimentary.\n  It could be improved largely, if `/cabal/store` could be copied out\n  after the build, and in before the build. I don't really know how to that\n  in Docker. Any tips are welcome.\n  For example with `docker run` one could use volumes, but not with `docker build`.\n\nFinally\n-------\n\nLook at [the example repository](https://github.com/phadej/docker-haskell-example).\nI hope this is useful for you.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphadej%2Fdocker-haskell-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphadej%2Fdocker-haskell-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphadej%2Fdocker-haskell-example/lists"}