diff --git a/DISTROLESS_COMPARISON.md b/DISTROLESS_COMPARISON.md new file mode 100644 index 0000000..d139ab8 --- /dev/null +++ b/DISTROLESS_COMPARISON.md @@ -0,0 +1,133 @@ +# Distroless Docker Options for APRS.me + +This document compares different distroless approaches for containerizing the APRS.me Elixir/Phoenix application. + +## Current Setup vs Distroless Options + +### Current Setup (`Dockerfile`) +- **Base Image**: `debian:bullseye-slim` (~80MB) +- **Security**: Good (non-root user, hardened) +- **Attack Surface**: Moderate (full Debian with package manager) +- **Debugging**: Easy (shell access, package manager) +- **Compatibility**: Excellent + +### Option 1: Simple Distroless (`Dockerfile.distroless-simple`) +- **Base Image**: `gcr.io/distroless/cc-debian11:nonroot` (~20MB) +- **Security**: Excellent (no shell, no package manager) +- **Attack Surface**: Minimal +- **Debugging**: Difficult (no shell access) +- **Compatibility**: Good for most Elixir applications +- **Recommended**: ✅ **BEST STARTING POINT** + +### Option 2: Custom Library Distroless (`Dockerfile.distroless`) +- **Base Image**: `gcr.io/distroless/base-debian11:nonroot` (~15MB) +- **Security**: Excellent +- **Attack Surface**: Minimal +- **Debugging**: Very difficult +- **Compatibility**: May require fine-tuning +- **Complexity**: High (manual library management) + +### Option 3: Static Distroless (`Dockerfile.distroless-static`) +- **Base Image**: `gcr.io/distroless/static-debian11:nonroot` (~2MB) +- **Security**: Maximum +- **Attack Surface**: Absolute minimum +- **Debugging**: Nearly impossible +- **Compatibility**: **May not work** with Erlang/OTP +- **Recommended**: ❌ **NOT RECOMMENDED** for Elixir + +## Recommended Migration Path + +### Phase 1: Test Simple Distroless +1. Use `Dockerfile.distroless-simple` +2. Test all application functionality +3. Monitor for any runtime issues +4. Verify external dependencies work (database, APIs) + +### Phase 2: Optimize (if needed) +1. If Phase 1 works perfectly, you're done! +2. If you need smaller images, consider custom library approach +3. Profile which libraries are actually needed + +## Key Benefits of Distroless + +### Security Improvements +- **No shell access**: Eliminates shell-based attacks +- **No package manager**: Cannot install malicious packages +- **Minimal attack surface**: Only your application + runtime +- **Reduced CVEs**: Fewer components = fewer vulnerabilities +- **Immutable**: Cannot be modified at runtime + +### Operational Benefits +- **Smaller images**: Faster pulls, less storage +- **Faster startup**: Less to initialize +- **Better compliance**: Meets strict security requirements +- **Supply chain security**: Google-maintained base images + +## Potential Challenges + +### Debugging Difficulties +```bash +# This won't work in distroless: +docker exec -it container /bin/bash + +# Use this instead: +docker run --rm -it --entrypoint="" your-image:tag /app/bin/server remote +``` + +### Limited Troubleshooting +- No shell for investigating issues +- Must rely on application logs +- Consider adding detailed logging/metrics + +### Dependency Issues +- Some NIFs might need additional libraries +- SSL/TLS certificates need careful handling +- Time zones might not be available + +## Testing Checklist + +Before switching to distroless, verify: + +- [ ] Application starts successfully +- [ ] Database connections work +- [ ] Phoenix LiveView functions +- [ ] Asset serving works +- [ ] SSL/HTTPS connections work +- [ ] External API calls succeed +- [ ] WebSocket connections work +- [ ] Health checks pass +- [ ] Graceful shutdown works +- [ ] Logging outputs correctly + +## Build Commands + +```bash +# Test simple distroless version +docker build -f Dockerfile.distroless-simple -t aprs:distroless-simple . + +# Compare image sizes +docker images | grep aprs + +# Test functionality +docker run -p 4000:4000 aprs:distroless-simple +``` + +## Rollback Plan + +If distroless causes issues: +1. Keep current `Dockerfile` as `Dockerfile.debian` +2. Switch CI/CD back to Debian version +3. Investigate specific compatibility issues +4. Gradually migrate features + +## Conclusion + +**Recommendation**: Start with `Dockerfile.distroless-simple` + +This provides: +- Significant security improvements +- Manageable complexity +- Good compatibility with Elixir/Phoenix +- Easy rollback if needed + +The `gcr.io/distroless/cc-debian11:nonroot` image includes the C runtime libraries that Erlang/OTP requires, making it the most compatible distroless option for Elixir applications. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b707c3b..4b17f1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,9 @@ ARG ELIXIR_VERSION=1.18.4 ARG OTP_VERSION=27.2.4 ARG DEBIAN_VERSION=bullseye-20250520-slim -# Set default non-root user and group IDs -ARG USER_ID=1000 -ARG GROUP_ID=1000 - ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" -ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" +# Use distroless base image - this includes glibc and essential libraries +ARG RUNNER_IMAGE="gcr.io/distroless/cc-debian11:nonroot" FROM ${BUILDER_IMAGE} AS builder @@ -34,15 +31,11 @@ RUN mix deps.get --only $MIX_ENV RUN mkdir config # copy compile-time config files before we compile dependencies -# to ensure any relevant config change will trigger the dependencies -# to be re-compiled. COPY config/config.exs config/${MIX_ENV}.exs config/ RUN mix deps.compile COPY priv priv - COPY lib lib - COPY assets assets # compile assets @@ -57,86 +50,33 @@ COPY config/runtime.exs config/ COPY rel rel RUN mix release -# start a new build stage so that the final image will only contain -# the compiled release and other runtime necessities +# Final distroless stage FROM ${RUNNER_IMAGE} -# Install security updates and required packages -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update -y && \ - apt-get upgrade -y && \ - apt-get install -y --no-install-recommends \ - libstdc++6 \ - openssl \ - libncurses5 \ - locales \ - ca-certificates \ - tini && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - # Create a non-root user and group with specific ID - groupadd -g 1000 aprs && \ - useradd -r -g aprs -u 1000 -s /bin/false -M aprs && \ - # Remove setuid and setgid permissions - find / -perm /6000 -type f -exec chmod a-s {} \; || true && \ - # Secure system configurations - chmod 0600 /etc/login.defs && \ - chmod 0600 /etc/passwd && \ - chmod 0600 /etc/group && \ - # Create and secure app directory - mkdir -p /app && \ - chown aprs:aprs /app && \ - chmod 0750 /app - -# Set the locale -RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -WORKDIR "/app" - -# Set security-related environment variables +# Set environment variables for production ENV MIX_ENV="prod" \ - LANG=en_US.UTF-8 \ - LANGUAGE=en_US:en \ - LC_ALL=en_US.UTF-8 \ - # Disable history files + LANG=C.UTF-8 \ + # Disable history files for security HISTFILE=/dev/null \ - # Prevent writing .erlang.cookie file - HOME=/dev/null \ - # Add security headers - SECURITY_HEADERS="true" \ - # Disable debug info in production - ERL_AFLAGS="+S 1:1 +A 1 +K true -kernel shell_history enabled -kernel shell_history_file_bytes 0" \ + # Set HOME to /tmp (writable in distroless) + HOME=/tmp \ + # Configure Erlang VM + ERL_AFLAGS="+S 1:1 +A 1 +K true" \ PHX_SERVER=true -# Only copy the final release from the build stage -COPY --from=builder --chown=1000:1000 /app/_build/${MIX_ENV}/rel/aprs ./ +WORKDIR /app -USER 1000 +# Copy the Elixir release from builder stage +# nonroot user in distroless has UID 65532 +COPY --from=builder --chown=65532:65532 /app/_build/prod/rel/aprs ./ -# Ensure the server binary is executable -RUN chmod +x /app/bin/server - -# If using an environment that doesn't automatically reap zombie processes, it is -# advised to add an init process such as tini via `apt-get install` -# above and adding an entrypoint. See https://github.com/krallin/tini for details -# ENTRYPOINT ["/tini", "--"] - -# Add specific capabilities needed by the app -CMD /app/bin/server - -# Add security-related metadata +# Add security metadata LABEL org.opencontainers.image.vendor="APRS.me" \ - org.opencontainers.image.title="APRS.me Server" \ - org.opencontainers.image.description="APRS.me server with security hardening" \ - org.opencontainers.image.version="${MIX_ENV}" \ - org.opencontainers.image.created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ - security.root-user="false" \ - security.non-root-user="app" \ - security.privileged="false" + org.opencontainers.image.title="APRS.me Server (Distroless)" \ + org.opencontainers.image.description="APRS.me server with distroless base" \ + security.distroless="true" \ + security.nonroot="true" -# Container configuration is handled by Dokku +# Run the server +ENTRYPOINT ["/app/bin/server"] EXPOSE $PORT diff --git a/Dockerfile.debian b/Dockerfile.debian new file mode 100644 index 0000000..b707c3b --- /dev/null +++ b/Dockerfile.debian @@ -0,0 +1,142 @@ +ARG ELIXIR_VERSION=1.18.4 +ARG OTP_VERSION=27.2.4 +ARG DEBIAN_VERSION=bullseye-20250520-slim + +# Set default non-root user and group IDs +ARG USER_ID=1000 +ARG GROUP_ID=1000 + +ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" + +FROM ${BUILDER_IMAGE} AS builder + +# install build dependencies +RUN apt-get update -y && \ + apt-get upgrade -y && \ + apt-get install -y build-essential git && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# prepare build dir +WORKDIR /app + +# install hex + rebar +RUN mix local.hex --force && \ + mix local.rebar --force + +# set build ENV +ENV MIX_ENV="prod" + +# install mix dependencies +COPY mix.exs mix.lock ./ +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +# copy compile-time config files before we compile dependencies +# to ensure any relevant config change will trigger the dependencies +# to be re-compiled. +COPY config/config.exs config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +COPY priv priv + +COPY lib lib + +COPY assets assets + +# compile assets +RUN mix assets.deploy + +# Compile the release +RUN mix compile + +# Changes to config/runtime.exs don't require recompiling the code +COPY config/runtime.exs config/ + +COPY rel rel +RUN mix release + +# start a new build stage so that the final image will only contain +# the compiled release and other runtime necessities +FROM ${RUNNER_IMAGE} + +# Install security updates and required packages +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update -y && \ + apt-get upgrade -y && \ + apt-get install -y --no-install-recommends \ + libstdc++6 \ + openssl \ + libncurses5 \ + locales \ + ca-certificates \ + tini && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + # Create a non-root user and group with specific ID + groupadd -g 1000 aprs && \ + useradd -r -g aprs -u 1000 -s /bin/false -M aprs && \ + # Remove setuid and setgid permissions + find / -perm /6000 -type f -exec chmod a-s {} \; || true && \ + # Secure system configurations + chmod 0600 /etc/login.defs && \ + chmod 0600 /etc/passwd && \ + chmod 0600 /etc/group && \ + # Create and secure app directory + mkdir -p /app && \ + chown aprs:aprs /app && \ + chmod 0750 /app + +# Set the locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR "/app" + +# Set security-related environment variables +ENV MIX_ENV="prod" \ + LANG=en_US.UTF-8 \ + LANGUAGE=en_US:en \ + LC_ALL=en_US.UTF-8 \ + # Disable history files + HISTFILE=/dev/null \ + # Prevent writing .erlang.cookie file + HOME=/dev/null \ + # Add security headers + SECURITY_HEADERS="true" \ + # Disable debug info in production + ERL_AFLAGS="+S 1:1 +A 1 +K true -kernel shell_history enabled -kernel shell_history_file_bytes 0" \ + PHX_SERVER=true + +# Only copy the final release from the build stage +COPY --from=builder --chown=1000:1000 /app/_build/${MIX_ENV}/rel/aprs ./ + +USER 1000 + +# Ensure the server binary is executable +RUN chmod +x /app/bin/server + +# If using an environment that doesn't automatically reap zombie processes, it is +# advised to add an init process such as tini via `apt-get install` +# above and adding an entrypoint. See https://github.com/krallin/tini for details +# ENTRYPOINT ["/tini", "--"] + +# Add specific capabilities needed by the app +CMD /app/bin/server + +# Add security-related metadata +LABEL org.opencontainers.image.vendor="APRS.me" \ + org.opencontainers.image.title="APRS.me Server" \ + org.opencontainers.image.description="APRS.me server with security hardening" \ + org.opencontainers.image.version="${MIX_ENV}" \ + org.opencontainers.image.created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + security.root-user="false" \ + security.non-root-user="app" \ + security.privileged="false" + +# Container configuration is handled by Dokku +EXPOSE $PORT diff --git a/Dockerfile.distroless b/Dockerfile.distroless new file mode 100644 index 0000000..d0d9adb --- /dev/null +++ b/Dockerfile.distroless @@ -0,0 +1,150 @@ +ARG ELIXIR_VERSION=1.18.4 +ARG OTP_VERSION=27.2.4 +ARG DEBIAN_VERSION=bullseye-20250520-slim + +# Set default non-root user and group IDs +ARG USER_ID=1000 +ARG GROUP_ID=1000 + +ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +# Use distroless base image with glibc for Erlang/Elixir +ARG RUNNER_IMAGE="gcr.io/distroless/base-debian11:nonroot" + +FROM ${BUILDER_IMAGE} AS builder + +# install build dependencies +RUN apt-get update -y && \ + apt-get upgrade -y && \ + apt-get install -y build-essential git && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# prepare build dir +WORKDIR /app + +# install hex + rebar +RUN mix local.hex --force && \ + mix local.rebar --force + +# set build ENV +ENV MIX_ENV="prod" + +# install mix dependencies +COPY mix.exs mix.lock ./ +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +# copy compile-time config files before we compile dependencies +# to ensure any relevant config change will trigger the dependencies +# to be re-compiled. +COPY config/config.exs config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +COPY priv priv + +COPY lib lib + +COPY assets assets + +# compile assets +RUN mix assets.deploy + +# Compile the release +RUN mix compile + +# Changes to config/runtime.exs don't require recompiling the code +COPY config/runtime.exs config/ + +COPY rel rel +RUN mix release + +# Create a temporary stage to prepare required libraries +FROM debian:bullseye-slim AS lib-extractor + +# Install packages needed to extract libraries +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + libstdc++6 \ + openssl \ + libncurses5 \ + libssl1.1 \ + libtinfo5 \ + libcrypto++6 \ + ca-certificates \ + && apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create directories for extracted libraries +RUN mkdir -p /tmp/distroless/lib /tmp/distroless/usr/lib /tmp/distroless/etc/ssl /tmp/distroless/usr/share + +# Copy required shared libraries +RUN cp -a /lib/x86_64-linux-gnu/libdl.so* /tmp/distroless/lib/ && \ + cp -a /lib/x86_64-linux-gnu/librt.so* /tmp/distroless/lib/ && \ + cp -a /lib/x86_64-linux-gnu/libpthread.so* /tmp/distroless/lib/ && \ + cp -a /lib/x86_64-linux-gnu/libm.so* /tmp/distroless/lib/ && \ + cp -a /lib/x86_64-linux-gnu/libc.so* /tmp/distroless/lib/ && \ + cp -a /usr/lib/x86_64-linux-gnu/libstdc++.so* /tmp/distroless/usr/lib/ && \ + cp -a /usr/lib/x86_64-linux-gnu/libssl.so* /tmp/distroless/usr/lib/ && \ + cp -a /usr/lib/x86_64-linux-gnu/libcrypto.so* /tmp/distroless/usr/lib/ && \ + cp -a /lib/x86_64-linux-gnu/libtinfo.so* /tmp/distroless/lib/ && \ + cp -a /usr/lib/x86_64-linux-gnu/libncurses.so* /tmp/distroless/usr/lib/ && \ + cp -a /lib/x86_64-linux-gnu/libz.so* /tmp/distroless/lib/ + +# Copy CA certificates +RUN cp -a /etc/ssl/certs /tmp/distroless/etc/ssl/ + +# Final distroless stage +FROM ${RUNNER_IMAGE} + +# Set environment variables for production +ENV MIX_ENV="prod" \ + LANG=C.UTF-8 \ + LANGUAGE=C:en \ + LC_ALL=C.UTF-8 \ + # Disable history files + HISTFILE=/dev/null \ + # Prevent writing .erlang.cookie file to writable location + HOME=/tmp \ + # Add security headers + SECURITY_HEADERS="true" \ + # Configure Erlang VM for production + ERL_AFLAGS="+S 1:1 +A 1 +K true -kernel shell_history enabled" \ + PHX_SERVER=true + +# Copy required shared libraries from lib-extractor stage +COPY --from=lib-extractor /tmp/distroless/lib/* /lib/x86_64-linux-gnu/ +COPY --from=lib-extractor /tmp/distroless/usr/lib/* /usr/lib/x86_64-linux-gnu/ +COPY --from=lib-extractor /tmp/distroless/etc/ssl /etc/ssl + +# Set working directory +WORKDIR /app + +# Copy the Elixir release from builder stage +# The distroless nonroot user has UID 65532, so we use that +COPY --from=builder --chown=65532:65532 /app/_build/prod/rel/aprs ./ + +# Create required directories with proper permissions +USER 0 +RUN mkdir -p /tmp && \ + chmod 1777 /tmp && \ + chmod +x /app/bin/server +USER 65532 + +# Add security-related metadata +LABEL org.opencontainers.image.vendor="APRS.me" \ + org.opencontainers.image.title="APRS.me Server (Distroless)" \ + org.opencontainers.image.description="APRS.me server with distroless base for maximum security" \ + org.opencontainers.image.version="prod" \ + security.root-user="false" \ + security.distroless="true" \ + security.privileged="false" \ + security.base-image="gcr.io/distroless/base-debian11:nonroot" + +# Distroless images use a different init system, no need for tini +# The application should handle graceful shutdowns internally + +# Command to run the application +ENTRYPOINT ["/app/bin/server"] + +# Container configuration is handled by Dokku +EXPOSE $PORT diff --git a/Dockerfile.distroless-simple b/Dockerfile.distroless-simple new file mode 100644 index 0000000..4b17f1f --- /dev/null +++ b/Dockerfile.distroless-simple @@ -0,0 +1,82 @@ +ARG ELIXIR_VERSION=1.18.4 +ARG OTP_VERSION=27.2.4 +ARG DEBIAN_VERSION=bullseye-20250520-slim + +ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +# Use distroless base image - this includes glibc and essential libraries +ARG RUNNER_IMAGE="gcr.io/distroless/cc-debian11:nonroot" + +FROM ${BUILDER_IMAGE} AS builder + +# install build dependencies +RUN apt-get update -y && \ + apt-get upgrade -y && \ + apt-get install -y build-essential git && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# prepare build dir +WORKDIR /app + +# install hex + rebar +RUN mix local.hex --force && \ + mix local.rebar --force + +# set build ENV +ENV MIX_ENV="prod" + +# install mix dependencies +COPY mix.exs mix.lock ./ +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +# copy compile-time config files before we compile dependencies +COPY config/config.exs config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +COPY priv priv +COPY lib lib +COPY assets assets + +# compile assets +RUN mix assets.deploy + +# Compile the release +RUN mix compile + +# Changes to config/runtime.exs don't require recompiling the code +COPY config/runtime.exs config/ + +COPY rel rel +RUN mix release + +# Final distroless stage +FROM ${RUNNER_IMAGE} + +# Set environment variables for production +ENV MIX_ENV="prod" \ + LANG=C.UTF-8 \ + # Disable history files for security + HISTFILE=/dev/null \ + # Set HOME to /tmp (writable in distroless) + HOME=/tmp \ + # Configure Erlang VM + ERL_AFLAGS="+S 1:1 +A 1 +K true" \ + PHX_SERVER=true + +WORKDIR /app + +# Copy the Elixir release from builder stage +# nonroot user in distroless has UID 65532 +COPY --from=builder --chown=65532:65532 /app/_build/prod/rel/aprs ./ + +# Add security metadata +LABEL org.opencontainers.image.vendor="APRS.me" \ + org.opencontainers.image.title="APRS.me Server (Distroless)" \ + org.opencontainers.image.description="APRS.me server with distroless base" \ + security.distroless="true" \ + security.nonroot="true" + +# Run the server +ENTRYPOINT ["/app/bin/server"] +EXPOSE $PORT diff --git a/Dockerfile.distroless-static b/Dockerfile.distroless-static new file mode 100644 index 0000000..eeffc95 --- /dev/null +++ b/Dockerfile.distroless-static @@ -0,0 +1,153 @@ +ARG ELIXIR_VERSION=1.18.4 +ARG OTP_VERSION=27.2.4 +ARG DEBIAN_VERSION=bullseye-20250520-slim + +ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +# Use static distroless image with only statically linked binaries +ARG RUNNER_IMAGE="gcr.io/distroless/static-debian11:nonroot" + +FROM ${BUILDER_IMAGE} AS builder + +# install build dependencies +RUN apt-get update -y && \ + apt-get upgrade -y && \ + apt-get install -y build-essential git && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# prepare build dir +WORKDIR /app + +# install hex + rebar +RUN mix local.hex --force && \ + mix local.rebar --force + +# set build ENV +ENV MIX_ENV="prod" + +# install mix dependencies +COPY mix.exs mix.lock ./ +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +# copy compile-time config files before we compile dependencies +COPY config/config.exs config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +COPY priv priv +COPY lib lib +COPY assets assets + +# compile assets +RUN mix assets.deploy + +# Compile the release +RUN mix compile + +# Changes to config/runtime.exs don't require recompiling the code +COPY config/runtime.exs config/ + +COPY rel rel +RUN mix release + +# Create intermediate stage to prepare statically linked binaries +FROM debian:bullseye-slim AS static-builder + +# Install tools to create static binaries +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + musl-tools \ + musl-dev \ + upx-ucl \ + file \ + && apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Copy the release from builder +COPY --from=builder /app/_build/prod/rel/aprs /app + +# Create a wrapper script that can be statically linked +RUN cat > /wrapper.c << 'EOF' +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + char *args[] = {"/app/bin/server", "start", NULL}; + execv("/app/bin/server", args); + perror("execv failed"); + return 1; +} +EOF + +# This approach won't work well with Erlang/Elixir as they require dynamic linking +# Let's use a different approach with a minimal runtime environment + +# Runtime preparation stage +FROM debian:bullseye-slim AS runtime-prep + +# Install only the absolute minimum runtime dependencies +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + libc6 \ + libssl1.1 \ + libcrypto++6 \ + libncurses5 \ + libtinfo5 \ + ca-certificates \ + && apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Copy necessary libraries to a clean location +RUN mkdir -p /runtime/lib/x86_64-linux-gnu /runtime/usr/lib/x86_64-linux-gnu /runtime/etc/ssl + +# Copy essential libraries +RUN cp -L /lib/x86_64-linux-gnu/libc.so.6 /runtime/lib/x86_64-linux-gnu/ && \ + cp -L /lib/x86_64-linux-gnu/libdl.so.2 /runtime/lib/x86_64-linux-gnu/ && \ + cp -L /lib/x86_64-linux-gnu/libpthread.so.0 /runtime/lib/x86_64-linux-gnu/ && \ + cp -L /lib/x86_64-linux-gnu/libm.so.6 /runtime/lib/x86_64-linux-gnu/ && \ + cp -L /lib/x86_64-linux-gnu/librt.so.1 /runtime/lib/x86_64-linux-gnu/ && \ + cp -L /lib/x86_64-linux-gnu/libtinfo.so.5 /runtime/lib/x86_64-linux-gnu/ && \ + cp -L /usr/lib/x86_64-linux-gnu/libssl.so.1.1 /runtime/usr/lib/x86_64-linux-gnu/ && \ + cp -L /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 /runtime/usr/lib/x86_64-linux-gnu/ && \ + cp -L /usr/lib/x86_64-linux-gnu/libncurses.so.5 /runtime/usr/lib/x86_64-linux-gnu/ && \ + cp -L /lib64/ld-linux-x86-64.so.2 /runtime/lib64/ || mkdir -p /runtime/lib64 && cp -L /lib64/ld-linux-x86-64.so.2 /runtime/lib64/ + +# Copy CA certificates +RUN cp -r /etc/ssl/certs /runtime/etc/ssl/ + +# Final static distroless stage +FROM ${RUNNER_IMAGE} + +# Copy minimal runtime environment +COPY --from=runtime-prep /runtime / + +# Set environment variables +ENV MIX_ENV="prod" \ + LANG=C.UTF-8 \ + PATH="/app/bin:$PATH" \ + HISTFILE=/dev/null \ + HOME=/tmp \ + ERL_AFLAGS="+S 1:1 +A 1 +K true" \ + PHX_SERVER=true + +WORKDIR /app + +# Copy the Elixir release +COPY --from=builder --chown=65532:65532 /app/_build/prod/rel/aprs ./ + +# Security labels +LABEL org.opencontainers.image.vendor="APRS.me" \ + org.opencontainers.image.title="APRS.me Server (Static Distroless)" \ + org.opencontainers.image.description="APRS.me server with static distroless base - maximum security" \ + security.distroless="true" \ + security.static="true" \ + security.nonroot="true" \ + security.no-shell="true" + +# Note: Static distroless is very restrictive and may not work with all Erlang features +# Consider using cc-debian11:nonroot for better compatibility + +ENTRYPOINT ["/app/bin/server"] +EXPOSE $PORT