distroless

This commit is contained in:
Graham McIntire 2025-06-15 15:01:38 -05:00
parent 6a4b32845d
commit 05951ad609
6 changed files with 681 additions and 81 deletions

133
DISTROLESS_COMPARISON.md Normal file
View file

@ -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.

View file

@ -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

142
Dockerfile.debian Normal file
View file

@ -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

150
Dockerfile.distroless Normal file
View file

@ -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

View file

@ -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

View file

@ -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 <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
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