# 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 ```bash # Build the image make build # Test it works make test # Push to registry make push ``` ## Usage ### Building the Base Image ```bash # 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: ```bash # 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 ```bash make test ``` Verifies: - Erlang/OTP is installed and working - Elixir is installed and working - Locale is configured correctly ### Pushing to Registries ```bash # 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`: ```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): ```dockerfile # 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`: ```bash 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: ```bash 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: ```yaml rebuild-base-image: stage: build only: - schedules # Run on schedule script: - cd k8s/base-image - make build - make push ``` ## Troubleshooting ### Image fails to build ```bash # 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`: ```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 - [hexpm/elixir Docker Images](https://hub.docker.com/r/hexpm/elixir) - [Elixir Installation Guide](https://elixir-lang.org/install.html) - [Docker Multi-Stage Builds](https://docs.docker.com/build/building/multi-stage/)