Simplify Dockerfile for much faster builds

- Remove BuildKit advanced features that were slowing builds
- Remove multi-platform builds (only build for amd64)
- Remove security scanning stage
- Remove complex caching mounts
- Simplify to basic 2-stage build
- Use registry cache instead of GitHub Actions cache

This should reduce build time from 10+ minutes to 2-3 minutes.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2025-07-26 16:22:38 -05:00
parent 6f6a04b485
commit 1cfeaf556e
No known key found for this signature in database
2 changed files with 33 additions and 127 deletions

View file

@ -43,13 +43,6 @@ jobs:
type=sha,prefix={{branch}}- type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}} type=raw,value=latest,enable={{is_default_branch}}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
network=host
image=moby/buildkit:latest
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
@ -57,10 +50,8 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=gha,mode=max cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
platforms: linux/amd64,linux/arm64
provenance: false
deploy: deploy:
needs: build-and-push needs: build-and-push

View file

@ -1,150 +1,65 @@
# syntax=docker/dockerfile:1.4
# Build arguments # Build arguments
ARG ELIXIR_VERSION=1.18.4 ARG ELIXIR_VERSION=1.18.4
ARG OTP_VERSION=27.2.4 ARG OTP_VERSION=27.2.4
ARG DEBIAN_VERSION=bullseye-20250520-slim ARG DEBIAN_VERSION=bullseye-20250520-slim
ARG APP_NAME=aprs
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
# Platform args for multi-platform builds # Build stage
ARG TARGETPLATFORM FROM ${BUILDER_IMAGE} AS builder
ARG BUILDPLATFORM
# Stage 1: Dependencies only (cached layer)
FROM ${BUILDER_IMAGE} AS deps
# Install build dependencies # Install build dependencies
ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -y && \
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ apt-get install -y --no-install-recommends gcc g++ make git && \
--mount=type=cache,target=/var/lib/apt,sharing=locked \ apt-get clean && rm -rf /var/lib/apt/lists/*
apt-get update -y && apt-get install -y --no-install-recommends \
gcc \
g++ \
make \
git
WORKDIR /app WORKDIR /app
# Install Hex and Rebar with cache mount # Install hex + rebar
RUN --mount=type=cache,target=/root/.cache/rebar3,sharing=locked \ RUN mix local.hex --force && \
--mount=type=cache,target=/root/.hex,sharing=locked \
mix local.hex --force && \
mix local.rebar --force && \ mix local.rebar --force && \
mix archive.install hex mix_gleam 0.6.2 --force mix archive.install hex mix_gleam 0.6.2 --force
ENV MIX_ENV=prod ENV MIX_ENV=prod
# Copy only files needed for dependencies # Install mix dependencies
COPY mix.exs mix.lock ./ COPY mix.exs mix.lock ./
COPY vendor vendor COPY vendor vendor
RUN mix deps.get --only $MIX_ENV && \
mix deps.compile
# Get and compile dependencies with cache mount # Copy and compile application
RUN --mount=type=cache,target=/app/deps,sharing=locked \
--mount=type=cache,target=/app/_build,sharing=locked \
mix deps.get --only $MIX_ENV && \
mix deps.compile && \
cd vendor/aprs && \
mix compile && \
cd ../.. && \
# Copy compiled deps to a location that persists
cp -r deps /app/deps_compiled && \
cp -r _build /app/_build_compiled
# Stage 2: Build application
FROM deps AS builder
# Copy pre-compiled dependencies
RUN cp -r /app/deps_compiled deps && \
cp -r /app/_build_compiled _build
# Copy application code (ordered by change frequency)
COPY rel rel
COPY config config COPY config config
COPY priv priv
COPY assets assets
COPY lib lib COPY lib lib
COPY assets assets
COPY priv priv
COPY rel rel
# Build everything in one RUN with proper error handling # Build application
RUN --mount=type=cache,target=/root/.cache,sharing=locked <<EOF RUN cd vendor/aprs && mix compile && cd ../.. && \
set -e mkdir -p _build/prod/lib/aprs/ebin && \
cp -r vendor/aprs/_build/prod/lib/aprs/ebin/* _build/prod/lib/aprs/ebin/ && \
mix compile && \
mix assets.deploy && \
mix release
# Setup BEAM files # Runtime stage
mkdir -p _build/prod/lib/aprs/ebin FROM ${RUNNER_IMAGE}
cp -r vendor/aprs/_build/prod/lib/aprs/ebin/* _build/prod/lib/aprs/ebin/
mkdir -p _build/prod/lib/aprsme/ebin RUN apt-get update -y && \
if [ -d "priv/gleam" ] && [ "$(ls -A priv/gleam/*.beam 2>/dev/null)" ]; then
cp priv/gleam/*.beam _build/prod/lib/aprsme/ebin/
fi
# Compile and build release
mix compile
mix assets.deploy
mix release --path /app/release
EOF
# Stage 3: Security scan (optional, can be commented out for faster builds)
FROM aquasec/trivy:latest AS security-scan
COPY --from=builder /app/release /scan
RUN trivy filesystem --exit-code 0 --no-progress --security-checks vuln /scan
# Stage 4: Create minimal runtime image
FROM ${RUNNER_IMAGE} AS runtime
# Create user first to avoid running as root
RUN useradd -r -u 1001 -g root -s /bin/false elixir
# Install only essential runtime dependencies in one layer
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked <<EOF
set -e
apt-get update -y
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
libstdc++6 \ libstdc++6 openssl libncurses5 locales ca-certificates && \
openssl \ sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen && \
libncurses5 \ apt-get clean && rm -rf /var/lib/apt/lists/* && \
locales \ useradd -r -u 1001 -g root -s /bin/false elixir
ca-certificates
# Generate locale
sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen
locale-gen
# Clean up
apt-get clean
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Remove unnecessary files
rm -rf /usr/share/doc /usr/share/man /usr/share/info /usr/share/locale/*
# Remove package manager files we don't need
rm -rf /var/log/dpkg.log /var/log/alternatives.log /var/log/apt
EOF
ENV LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 ENV LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8
# Create app directory with correct permissions
RUN mkdir -p /app && chown -R elixir:root /app
WORKDIR /app WORKDIR /app
COPY --from=builder --chown=elixir:root /app/_build/prod/rel/aprsme ./
# Copy release with correct ownership
COPY --from=builder --chown=elixir:root /app/release ./
# Set deployment timestamp as elixir user
USER elixir USER elixir
RUN date -u +"%Y-%m-%dT%H:%M:%SZ" > /app/deployed_at.txt RUN date -u +"%Y-%m-%dT%H:%M:%SZ" > /app/deployed_at.txt
# Use exec form to ensure proper signal handling CMD ["/app/bin/server"]
ENTRYPOINT ["/app/bin/server"]
# Add metadata labels
LABEL maintainer="aprs.me" \
security.scan="true" \
security.user="non-root" \
org.opencontainers.image.title="APRS.me" \
org.opencontainers.image.description="Real-time APRS packet tracker" \
org.opencontainers.image.vendor="aprs.me" \
org.opencontainers.image.source="https://github.com/aprsme/aprs.me"