towerops/k8s/base-image/README.md
Graham McIntire 7685fa53d1
fix: use official hexpm/elixir images instead of erlang-solutions repo
The Erlang Solutions APT repository was experiencing 504 Gateway Timeout
errors during builds, making the image build process unreliable.

Changes:
- Use official hexpm/elixir Docker image as builder stage
- More reliable (official Docker Hub images)
- Faster builds (pre-built binaries, no package installation)
- Still produces minimal runtime-only final image
- Updated documentation to reflect new approach

Benefits:
- No dependency on erlang-solutions CDN availability
- Faster build times (no apt-get install of large packages)
- Same minimal final image size (~150-200 MB)
- Easier to specify exact Erlang/Elixir versions
2026-01-25 09:21:42 -06:00

5.9 KiB

Minimal Debian Base Image for Elixir/Phoenix

This directory contains scripts to build a minimal Debian 13 (Trixie) base image with Erlang/OTP and Elixir runtime pre-installed.

Container Engine Support

The build scripts automatically detect and use either Podman or Docker, preferring Podman if both are installed. No configuration needed - just install your preferred container engine and the scripts will use it.

Why a Custom Base Image?

Building a custom base image provides several benefits:

  1. Faster CI/CD - Base image is cached, no need to reinstall Erlang/Elixir on every build
  2. Smaller Images - Only includes essential runtime dependencies
  3. Security - Regularly rebuild with latest security patches
  4. Consistency - Same runtime environment across dev/staging/production

Quick Start

# Build the image
make build

# Test it works
make test

# Push to registry
make push

Usage

Building the Base Image

# Using Makefile (recommended)
make build

# Or using the script directly
chmod +x build.sh
./build.sh

This builds a multi-stage Docker image with:

  • Builder stage: Uses official hexpm/elixir image with pre-built Erlang/Elixir
  • Final stage: Minimal Debian with only runtime dependencies copied from builder

Updating System Packages

Run this weekly/monthly to get latest security updates:

# Using Makefile
make update

# Or using the script
./update.sh

This rebuilds the image with --no-cache --pull to ensure all packages are updated.

Testing the Image

make test

Verifies:

  • Erlang/OTP is installed and working
  • Elixir is installed and working
  • Locale is configured correctly

Pushing to Registries

# Login to registries (one-time setup)
make login            # GitLab Container Registry
make login-dockerhub  # Docker Hub

# Push to both GitLab and Docker Hub
make push

Automatically pushes to both:

  • GitLab Container Registry: registry.gitlab.com/towerops/towerops/elixir-runtime
  • Docker Hub: docker.io/gmcintire/elixir-runtime

Both versioned and :latest tags are pushed to each registry.

Image Tags

Each build creates three tags:

  1. Full version: 28.3-elixir-1.19.5-debian-trixie (pinned, immutable)
  2. Latest: latest (always points to most recent build)
  3. Dated: 28.3-elixir-1.19.5-debian-trixie-2026-01-25 (for rollback)

Using the Base Image

Update your main k8s/Dockerfile:

# Before
ARG RUNNER_IMAGE="docker.io/debian:trixie-20251229-slim"

# After
ARG RUNNER_IMAGE="registry.gitlab.com/towerops/towerops/elixir-runtime:28.3-elixir-1.19.5-debian-trixie"

Then remove the runtime installation steps (already in base image):

# Remove these lines - they're now in the base image
# RUN apt-get update && apt-get install -y erlang elixir ...
# RUN locale-gen ...

Image Size

The final image is approximately 150-200 MB (vs ~120 MB for plain Debian).

This is significantly smaller than:

  • Official Elixir images (~400-500 MB)
  • Debian with manually installed Erlang/Elixir (~300 MB)

What's Included

Erlang/OTP Runtime

  • Latest Erlang/OTP from Erlang Solutions repository
  • All essential runtime libraries (ssl, crypto, etc.)
  • No build tools or dev dependencies

Elixir Runtime

  • Latest Elixir release
  • Mix build tool
  • IEx interactive shell

System Packages (Minimal Set)

  • libssl3 - SSL/TLS support
  • libsctp1 - SCTP protocol (required by Erlang)
  • libncurses6 - Terminal handling
  • libstdc++6 - C++ standard library
  • ca-certificates - SSL certificate validation
  • locales - Locale support (en_US.UTF-8)
  • iputils-ping - Network diagnostics
  • iproute2 - Network utilities

Configuration

Environment variables in build.sh:

REGISTRY=registry.gitlab.com/towerops/towerops
IMAGE_NAME=elixir-runtime
ERLANG_VERSION=28.3
ELIXIR_VERSION=1.19.5
DEBIAN_VERSION=trixie-20251229-slim

Override during build:

ERLANG_VERSION=29.0 ELIXIR_VERSION=1.20.0 make build

Maintenance Schedule

Recommended rebuild schedule:

  • Weekly: Security updates (make update)
  • Monthly: Review Erlang/Elixir versions, upgrade if needed
  • As needed: When CVEs are announced for included packages

CI/CD Integration

Add to GitLab CI to rebuild weekly:

rebuild-base-image:
  stage: build
  only:
    - schedules  # Run on schedule
  script:
    - cd k8s/base-image
    - make build
    - make push

Troubleshooting

Image fails to build

# Check if hexpm images are accessible
docker pull hexpm/elixir:1.19.5-erlang-28.3-debian-trixie-20251229-slim

# Try building with verbose output
docker build --progress=plain --no-cache -f Dockerfile .

Application fails with missing dependencies

Add missing package to Dockerfile:

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       your-missing-package \
    && rm -rf /var/lib/apt/lists/*

Then rebuild: make build

DNS resolution issues (unlikely with Debian)

If you experience DNS issues (should not happen with Debian, only Alpine):

  1. Check /etc/resolv.conf in running container
  2. Verify Kubernetes DNS is working
  3. Consider switching to Debian (already using it)

Comparison: Debian vs Alpine

We use Debian instead of Alpine because:

Debian Advantages:

  • Glibc (standard C library) - better compatibility
  • No DNS resolution issues with Erlang
  • Larger package ecosystem
  • Better tested with Erlang/Elixir

Alpine Disadvantages:

  • Musl libc - can cause subtle bugs with Erlang NIFs
  • DNS resolution issues with :inet and libcluster
  • Smaller package selection
  • Only ~50 MB smaller (not worth the issues)

See Also