Haskell与Docker部署镜像合理大小及优化方案咨询
Absolutely, your hunch to extract the compiled executable into a leaner base image is exactly the right move—this is the go-to strategy for slimming down Haskell Docker images, especially for deployments like Google App Engine. Let’s walk through the optimized approach step by step, plus some extra tweaks to get that image size way more reasonable.
The biggest win comes from splitting your Dockerfile into two stages: a build stage (where you compile your code with all the heavy tooling) and a runtime stage (where you only keep the executable and minimal dependencies). Here’s a concrete example tailored to Happstack and Stack:
# ------------------------------ # Build Stage: Compile the app # ------------------------------ FROM haskell:9.4-alpine as builder WORKDIR /app # Copy Stack config files first to leverage Docker cache COPY stack.yaml stack.yaml.lock package.yaml ./ RUN stack build --only-dependencies --system-ghc # Copy source code COPY src/ ./src/ COPY app/ ./app/ # Build the executable, copy it to a dedicated directory RUN stack build --system-ghc --optimize --copy-bins --local-bin-path /app/compiled # ------------------------------ # Runtime Stage: Minimal image # ------------------------------ FROM alpine:3.18 # Install critical system dependencies (Haskell apps often need libgmp) RUN apk add --no-cache libgmp # Copy the compiled executable from the build stage COPY --from=builder /app/compiled/your-happstack-app /usr/local/bin/ # Expose the port your Happstack app uses (default is 8000) EXPOSE 8000 # Set the entrypoint to run your app ENTRYPOINT ["/usr/local/bin/your-happstack-app"]
With this setup, all the heavyweight tools (Stack, GHC, intermediate build artifacts) stay in the builder stage and never make it to your final deployment image. You’ll end up with a runtime image that’s usually under 100MB—a massive drop from your original 3GB.
- Static Linking: If your dependencies support it, add the
--staticflag to yourstack buildcommand (e.g.,stack build --system-ghc --static --optimize). This creates a fully self-contained executable, letting you use an even smaller base image likescratch(though you’ll need to ensure no dynamic dependencies are left—useldd /app/compiled/your-happstack-appin the builder stage to check). - Enable Optimizations: The
--optimizeflag doesn’t just make your app faster—it also compiles code more efficiently, which can trim a few extra MB from the executable. You can also setoptimize: truepermanently in yourstack.yaml. - Clean Up Build Cache: In the builder stage, after compiling, run
rm -rf ~/.stackto delete Stack’s cached packages. This won’t affect the final image, but it makes the builder image smaller and faster to transfer if you’re reusing it.
The default haskell image is based on Debian, which is pretty bulky. Swap it for an Alpine-based variant like haskell:9.4-alpine or the specialized fpco/stack-build:lts-alpine image—both are significantly smaller and still have all the tools you need for Stack builds.
- Make sure your app listens on the correct port: GAE expects apps to listen on the port specified by the
PORTenvironment variable, so update your Happstack code to usegetEnv "PORT"(falling back to 8000 for local testing). - Double-check your
app.yamlconfiguration to point to the correct Dockerfile and port.
With these changes, you should easily get your deployment image down to a few hundred MB at most—way more reasonable for GAE.
内容的提问来源于stack exchange,提问作者ryachza

