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