distroless
This commit is contained in:
parent
6a4b32845d
commit
05951ad609
6 changed files with 681 additions and 81 deletions
133
DISTROLESS_COMPARISON.md
Normal file
133
DISTROLESS_COMPARISON.md
Normal 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.
|
||||
102
Dockerfile
102
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
|
||||
|
|
|
|||
142
Dockerfile.debian
Normal file
142
Dockerfile.debian
Normal 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
150
Dockerfile.distroless
Normal 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
|
||||
82
Dockerfile.distroless-simple
Normal file
82
Dockerfile.distroless-simple
Normal 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
|
||||
153
Dockerfile.distroless-static
Normal file
153
Dockerfile.distroless-static
Normal 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
|
||||
Loading…
Add table
Reference in a new issue