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

242 lines
5.9 KiB
Markdown

# 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/)