-
Notifications
You must be signed in to change notification settings - Fork 425
docs(docker): add an example of building distroless image #1900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
I don't understand the point of this. If you want the smallest possible file size, you want to go for a static musl binary with a bare linux kernel system. Statically linking everything ends up much smaller than linking against shared libraries, because dead code is stripped out, rather than preserved for dynamic exports. And at that point, you don't need Docker anymore. What you documented here will most definitely blow up when you attempt to use anything that loads a shared library. The docker container links against all libraries dynamically. And by using bookworm as the base, it itself wouldn't even run on a real "distroless" image, only on the stripped debian ones, because it links dynamically against glibc. |
|
Thanks for your feedback 🙏.
The main benefit of distroless images are security and image size : getting only things needed guarantees a minimal attack surface as it contains only your app and runtime deps (no package manager/shell/debugging tools/not even an echo).
You’re right, that’s why I’m using the same Debian version between builder and distroless stage and copying the entire shared library. Do you have anything in mind that could help ? |
Very minimal when using mimalloc. Even with the default allocator, you're only going to notice differences in heavily threaded scenarios. Using higher optimisation options will yield you 10%+++ better performance over debians/ubuntus php distributions, not to mention PGO. The gnu static builder supports the same options as the musl one.
Not particularly, but this approach seems like it will inevitably break things at some point. If someone is so starven of storage space that 6mb less (vs alpine) make a difference, perhaps they shouldn't be using frankenphp with a 50mb footprint, plus php with a ~35mb footprint. |
|
Given that Debian 12 is actually bookworm and all shared libraries are copied over, this would actually work. I need to retract my statement about this likely leading to crashes. Since distroless and hardened images are all the hype now, it might be useful to tidy this up a bit and get it merged. @damienfern are you willing to add |
| FROM dunglas/frankenphp:php8.3-bookworm AS builder | ||
| # Do everything you need here, like installing PHP extensions and copying your config files | ||
| FROM gcr.io/distroless/base-debian12:nonroot AS distroless | ||
| WORKDIR /app | ||
| # Get FrankenPHP and PHP binary from builder image | ||
| COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp | ||
| COPY --from=builder /usr/local/bin/php /usr/local/bin/php | ||
| # Copy PHP extensions and configuration from builder image | ||
| COPY --from=builder /usr/local/lib/php /usr/local/lib/php | ||
| COPY --from=builder /usr/local/lib/libphp.so /lib/libphp.so | ||
| COPY --from=builder /usr/local/lib/libwatcher-c.* /lib/ | ||
| COPY --from=builder /usr/local/etc/php /usr/local/etc/php | ||
| COPY --from=builder /usr/lib/ /usr/lib/ | ||
| COPY --from=builder /lib/ /lib/ | ||
| USER nonroot:nonroot | ||
| # Copy application files and Caddyfile | ||
| COPY --from=builder --chown=nonroot:nonroot /app /app | ||
| COPY --from=builder --chown=nonroot:nonroot /etc/caddy/Caddyfile /etc/caddy/Caddyfile | ||
| EXPOSE 80 443 | ||
| ENTRYPOINT [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@henderkes WDYT about explicitly only copying linked libraries via libtree?
FROM dunglas/frankenphp AS builder
# Add additional PHP extensions here
RUN install-php-extensions pdo_mysql pdo_pgsql #...
# copy shared libs of frankenphp and all installed extensions to temporary location
# you can also copy all libraries directly instead
RUN apt-get update && apt-get install -y libtree && \
EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
FRANKENPHP_BIN="$(which frankenphp)"; \
LIBS_TMP_DIR="/tmp/libs"; \
mkdir -p "$LIBS_TMP_DIR"; \
for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
[ -z "$lib" ] && continue; \
base=$(basename "$lib"); \
destfile="$LIBS_TMP_DIR/$base"; \
if [ ! -f "$destfile" ]; then \
cp "$lib" "$destfile"; \
fi; \
done; \
done
# distroless debian base image
FROM gcr.io/distroless/base-debian13
# docker hardened image alternative
# FROM dhi.io/debian:13
# define location of your app and Caddyfile, they will be copied into /app
ARG PATH_TO_APP="."
ARG PATH_TO_CADDYFILE="./Caddyfile"
# copy frankenphp and necessary libs
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
COPY --from=builder --chown=nonroot:nonroot /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=builder /tmp/libs /usr/lib
# copy php.ini configuration files
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
# copy necessary writable caddy dirs and Caddyfile
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
# copy your app and Caddyfile into /app
# make sure all writable paths are owned by the nonroot user
COPY "$PATH_TO_APP" /app
# run as non-root user for additional hardening
USER nonroot
WORKDIR /app
# entrypoint to run frankenphp with the provided Caddyfile
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@henderkes WDYT about explicitly only copying linked libraries via libtree?
- snip - RUN apt-get update && apt-get install -y libtree && \ EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \ FRANKENPHP_BIN="$(which frankenphp)"; \ LIBS_TMP_DIR="/tmp/libs"; \ mkdir -p "$LIBS_TMP_DIR"; \ for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \ libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \ [ -z "$lib" ] && continue; \ base=$(basename "$lib"); \ destfile="$LIBS_TMP_DIR/$base"; \ if [ ! -f "$destfile" ]; then \ cp "$lib" "$destfile"; \ fi; \ done; \ done - snip -
Good idea!
Related to #151, this PR adds an example about how building a distroless image for a Frankenphp project.
FYI, on https://github.com/dunglas/symfony-docker, it only saves ~24MB in the image size.
I think we could go even further by building our own distroless image with Bazel like they do here but for now, the doc is a good start.