Skip to content

Instantly share code, notes, and snippets.

@and-pete
Created August 30, 2020 13:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save and-pete/e8fd63d51121c61d25461bcc2b3fe743 to your computer and use it in GitHub Desktop.
Save and-pete/e8fd63d51121c61d25461bcc2b3fe743 to your computer and use it in GitHub Desktop.
Multi-Stage Docker Build for a Haskell (Stack) Project
# The below is a combination of the following two things:
# 1) This article + corresponding Gist: https://medium.com/permutive/optimized-docker-builds-for-haskell-76a9808eb10b
# 2) This Reddit comment in response to the above:
# - https://www.reddit.com/r/haskell/comments/cl5uod/optimized_docker_builds_for_haskell/evuzccm/
# It is a multi-stage Docker build with 3 stages: 1) dependencies, 2) build, and 3) deploy
# -------------------------------------------------------------------------------------------
# STAGE 1: Dependencies
# -------------------------------------------------------------------------------------------
FROM haskell:8.10.2 as dependencies
RUN mkdir /opt/build
WORKDIR /opt/build
# GHC dynamically links its compilation targets to lib gmp
RUN apt-get update -y \
&& apt-get download -y libgmp10
RUN mv libgmp*.deb libgmp.deb
# First build long-term stable dependencies using YAML/etc. files you do not expect to change often
# Keep those locally in a /long-term/ sub-directory to your project's root path
COPY ./long-term/stack.yaml ./long-term/package.yaml ./long-term/stack.yaml.lock ./
RUN stack build --dependencies-only
# If the current .yaml files in your project root have not changed from the long-term versions,
# Docker should use the cached version from the above layer. If they HAVE changed, Docker will
# rebuild the dependencies. If those changes are permanent, move the YAMLs into new /long-term/
COPY ./stack.yaml ./package.yaml ./stack.yaml.lock ./
RUN stack build --dependencies-only
# In the 'haskell' image, stack is configured with
# - system-ghc: true
# - install-ghc: false
# If an error is thrown by stack saying that it cannot find the correct GHC, then:
# - You might have tried a `stack build`/etc. command in a folder lacking
# a stack.yaml that correspnds to the version indicated in FROM above
# - Stack then defaults to creating a ~/.stack/global-config/stack.yaml
# from the most recent LTS resolver
# - And will expect to find whatever compiler version that LTS points to
# - This will happen if you built in a different WORKDIR than where you copied the YAML files
# -------------------------------------------------------------------------------------------
# STAGE 2: Build
# -------------------------------------------------------------------------------------------
FROM haskell:8.10.2 as build
# Copy stack's cache of your project's compiled dependencies
# from the previous build stage to the container
COPY --from=dependencies /root/.stack /root/.stack
# Change the container working directory to where you want to copy your project files
WORKDIR /opt/build
# Copy the entire contents of your local project folder
# to this new container (...and not just the YAMLs)
COPY . .
# Build your project
RUN stack build
# Move your compiled Haskell binary to a path easily accessible from the next stage
RUN mv "$(stack path --local-install-root --system-ghc)/bin" /opt/build/bin
# -------------------------------------------------------------------------------------------
# STAGE 3: Deploy
# -------------------------------------------------------------------------------------------
# Copy the compiled Haskell binary to a more lightweight (and less diskspace-hungry) image
# for uploading to a repository or using in deployment. 'ubuntu' is not the base image for
# the 'haskell' image, so this should probably be changed to 'debian'/etc.
FROM ubuntu:20.04 as deploy
RUN mkdir -p /opt/app
WORKDIR /opt/app
# Install libgmp using the .deb from the dependencies stage
# To-Do: Find a way to ensure the same version of libgmp is used as in the previous
# two build stages. Have experience a downgrade at this step before. Changing
# from Ubuntu to Debian for this final stage would probably help here.
COPY --from=dependencies /opt/build/libgmp.deb /tmp
RUN dpkg -i /tmp/libgmp.deb && rm /tmp/libgmp.deb
# Copy your compiled program from the build stage
COPY --from=build /opt/build/bin .
# Execute your app
CMD ["/opt/app/my-dummy-app", "--verbose"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment