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
242 lines
5.9 KiB
Markdown
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/)
|