The official Crystal-lang Docker image is Ubuntu-based and relatively large. However, production-ready images can be significantly smaller by utilizing Docker multi-stage builds. The smallest possible image can be created using the scratch base image. If some pre-execution processing or shell utilities are needed, BusyBox or Alpine Linux are recommended alternatives.
Dockerfile Example
To create a minimal image, compile the application using the official Crystal-lang image in an initial stage, and then copy the resulting binary (and any necessary dynamic libraries) to a scratch
image in a subsequent stage, as shown below:
# vim:set ft=dockerfile:
FROM crystallang/crystal:0.31.1 as builder
LABEL maintainer="Andrius Kairiukstis <****>"
WORKDIR /src
COPY . .
RUN shards build --production --progress --verbose --warnings=all
# Identify and copy runtime dependencies
RUN ldd ./bin/app | tr -s '[:blank:]' '\n' | grep '^/' | \
xargs -I % sh -c 'mkdir -p $(dirname deps%); cp -L % deps%;' # Use -L to copy actual files for symlinks
# RUN find ./deps/
################################################################################
# Final stage: copy artifacts to scratch image
FROM scratch
LABEL maintainer="Andrius Kairiukstis <****>"
# Copy runtime dependencies. These are crucial for DNS resolution in Docker.
COPY --from=builder /src/deps/ /
# Specifically copy DNS-related libraries if not covered by the general deps copy
# Ensure these paths are correct for the builder image (crystallang/crystal:0.31.1)
COPY --from=builder /lib/x86_64-linux-gnu/libnss_dns.so.2 /lib/x86_64-linux-gnu/
COPY --from=builder /lib/x86_64-linux-gnu/libresolv.so.2 /lib/x86_64-linux-gnu/
# Copy the compiled application
COPY --from=builder /src/bin/app /app
ENTRYPOINT ["/app"]
Crystal-lang Notes on DNS Resolution
A common issue with minimal Crystal-lang Docker images (especially those based on scratch
) is DNS resolution. This is documented in Crystal-lang GitHub issues such as #2426 and #6099.
The most effective solution I’ve found involves copying necessary libnss_*.so
and libresolv.so
files from the builder image, as demonstrated in the Dockerfile above and discussed in this Gist comment.
Attempting to compile with the --static
flag, even when including these DNS libraries, did not consistently resolve DNS issues in my tests. Statically linked images without these libraries are generally only suitable for services that do not require external DNS lookups (e.g., purely listening services).
Resulting Image Sizes
The resulting image sizes are significantly reduced:
- scratch-based with ldd dependencies (DNS working): ~10MB
- scratch-based, statically linked (DNS not working): ~6.25MB
$ docker image list dial_demo
# REPOSITORY TAG IMAGE ID CREATED SIZE
# smallest-docker-image scratch-ldd d819bf2a43f3 21 minutes ago 10MB
# smallest-docker-image scratch-static-no-dns 595afcfad6f0 13 minutes ago 6.25MB
Download Examples
Example Dockerfiles demonstrating these techniques are available in my sandbox repository on GitHub. This includes configurations for:
- scratch (ldd dependencies) - ~10MB
- BusyBox (ldd dependencies) - ~11.2MB
- Alpine Linux (ldd dependencies) - ~15.6MB