feat: create builder and runtime base images for faster deploys

Instead of installing system packages and tools on every deploy, we now
have two base images that pre-bake all the slow setup steps.

Base Images Created:
1. elixir-builder:latest - Builder stage base
   - hexpm/elixir with build-essential and git pre-installed
   - hex and rebar pre-installed
   - Saves ~40 seconds per deploy

2. debian-runtime:latest - Runtime stage base
   - Debian with all runtime packages pre-installed
   - Locale pre-configured (en_US.UTF-8)
   - /app directory pre-created
   - Saves ~60 seconds per deploy

Main Dockerfile Changes:
- Use elixir-builder:latest instead of hexpm/elixir
- Use debian-runtime:latest instead of debian:trixie-slim
- Remove apt-get install steps (now in base images)
- Remove hex/rebar install (now in builder base)
- Remove locale setup (now in runtime base)

Build System:
- build.sh builds both images with podman/docker auto-detect
- Pushes to both GitLab registry and Docker Hub
- Makefile targets for build/test/push/clean

Total Time Saved Per Deploy: ~100 seconds (1m40s)

Benefits:
- Faster CI/CD builds (no repeated apt-get install)
- Faster local development builds
- Consistent build environment across all deploys
- Security updates centralized in base image rebuilds
This commit is contained in:
Graham McIntire 2026-01-25 09:29:22 -06:00
parent 26a3b39edd
commit edd64479df
No known key found for this signature in database
4 changed files with 201 additions and 162 deletions

View file

@ -17,27 +17,18 @@ ARG ELIXIR_VERSION=1.19.4
ARG OTP_VERSION=28.3
ARG DEBIAN_VERSION=trixie-20251229-slim
ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="docker.io/debian:${DEBIAN_VERSION}"
# Use custom base images with pre-installed dependencies for faster builds
ARG BUILDER_IMAGE="registry.gitlab.com/towerops/towerops/elixir-builder:latest"
ARG RUNNER_IMAGE="registry.gitlab.com/towerops/towerops/debian-runtime:latest"
FROM ${BUILDER_IMAGE} AS builder
ENV MIX_OS_DEPS_COMPILE_PARTITION_COUNT=6
# install build dependencies
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential git \
&& rm -rf /var/lib/apt/lists/*
# prepare build dir
WORKDIR /app
# install hex + rebar
RUN mix local.hex --force \
&& mix local.rebar --force
# set build ENV
ENV MIX_ENV="prod"
# Base image already has:
# - build-essential and git installed
# - hex and rebar installed
# - MIX_OS_DEPS_COMPILE_PARTITION_COUNT=6
# - MIX_ENV="prod"
# - WORKDIR /app
# install mix dependencies
COPY mix.exs mix.lock ./
@ -80,21 +71,11 @@ RUN mix release
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE} AS final
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates iputils-ping \
&& rm -rf /var/lib/apt/lists/*
# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \
&& locale-gen
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
WORKDIR "/app"
RUN chown nobody /app
# Base image already has:
# - All runtime dependencies installed (libstdc++6, openssl, libncurses6, locales, ca-certificates, iputils-ping)
# - Locale configured (en_US.UTF-8)
# - /app directory created and owned by nobody
# - WORKDIR "/app"
# set runner ENV
ENV MIX_ENV="prod"

View file

@ -1,59 +1,66 @@
# Minimal Debian 13 (Trixie) with Erlang/OTP and Elixir runtime
# Uses official hexpm/elixir images as builder for reliability
# Multi-stage base images for Towerops Phoenix app
# Pre-bakes all the setup from k8s/Dockerfile so app deploys are faster
#
# This image is designed to be rebuilt regularly to get latest security updates
ARG ELIXIR_VERSION=1.19.4
ARG OTP_VERSION=28.3
ARG DEBIAN_VERSION=trixie-20251229-slim
ARG ERLANG_VERSION=28.3
ARG ELIXIR_VERSION=1.19.5
# Use official Elixir image as builder (more reliable than erlang-solutions repo)
FROM docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${ERLANG_VERSION}-debian-${DEBIAN_VERSION} as builder
# Verify installations in builder
RUN erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell
RUN elixir --version
# Final minimal runtime image
FROM docker.io/debian:${DEBIAN_VERSION}
#
# BUILDER BASE IMAGE
# Pre-installs build dependencies, hex, and rebar so every deploy is faster
#
FROM docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION} AS builder
LABEL maintainer="towerops"
LABEL description="Minimal Debian 13 with Erlang/OTP and Elixir runtime"
LABEL description="Elixir builder base with pre-installed build tools"
# Copy Erlang and Elixir from builder (hexpm image uses /usr/local)
COPY --from=builder /usr/local/lib/erlang /usr/local/lib/erlang
COPY --from=builder /usr/local/lib/elixir /usr/local/lib/elixir
# Set parallel compilation
ENV MIX_OS_DEPS_COMPILE_PARTITION_COUNT=6
# Copy all Erlang/Elixir binaries (they're symlinks to lib dirs)
COPY --from=builder /usr/local/bin/erl /usr/local/bin/erl
COPY --from=builder /usr/local/bin/erlc /usr/local/bin/erlc
COPY --from=builder /usr/local/bin/escript /usr/local/bin/escript
COPY --from=builder /usr/local/bin/epmd /usr/local/bin/epmd
COPY --from=builder /usr/local/bin/elixir /usr/local/bin/elixir
COPY --from=builder /usr/local/bin/elixirc /usr/local/bin/elixirc
COPY --from=builder /usr/local/bin/iex /usr/local/bin/iex
COPY --from=builder /usr/local/bin/mix /usr/local/bin/mix
# Install only essential runtime dependencies
# This is the absolute minimum needed for Erlang/Elixir + Phoenix apps
# Install build dependencies (matches k8s/Dockerfile builder stage exactly)
# This step takes ~30 seconds on every deploy, so we bake it in
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
# Erlang runtime dependencies
libssl3 \
libsctp1 \
libncurses6 \
libstdc++6 \
# System utilities
ca-certificates \
locales \
# Network utilities (minimal set)
iputils-ping \
iproute2 \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
build-essential \
git \
&& rm -rf /var/lib/apt/lists/*
# Configure locale (required for proper string handling)
# Install hex + rebar (matches k8s/Dockerfile exactly)
# This step takes ~10 seconds on every deploy, so we bake it in
RUN mix local.hex --force \
&& mix local.rebar --force
# Set build ENV
ENV MIX_ENV="prod"
WORKDIR /app
#
# RUNTIME BASE IMAGE
# Pre-installs runtime dependencies and configures locale
#
FROM docker.io/debian:${DEBIAN_VERSION} AS runtime
LABEL maintainer="towerops"
LABEL description="Debian 13 runtime base with pre-installed system packages and locale"
# Install runtime dependencies (matches k8s/Dockerfile runner stage exactly)
# This step takes ~30-60 seconds on every deploy, so we bake it in
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
libstdc++6 \
openssl \
libncurses6 \
locales \
ca-certificates \
iputils-ping \
&& rm -rf /var/lib/apt/lists/*
# Configure locale (matches k8s/Dockerfile exactly)
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \
&& locale-gen
@ -61,15 +68,6 @@ ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
# Add nobody user for security (already exists in Debian)
RUN mkdir -p /app && chown nobody:nogroup /app
WORKDIR /app
USER nobody
# Verify everything works
RUN erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell
RUN elixir --version
# Default command - override in application Dockerfile
CMD ["/bin/bash"]
# Set up /app directory (matches k8s/Dockerfile exactly)
WORKDIR "/app"
RUN chown nobody /app

View file

@ -5,10 +5,12 @@ CONTAINER_CMD := $(shell command -v podman 2>/dev/null || command -v docker 2>/d
REGISTRY ?= registry.gitlab.com/towerops/towerops
DOCKERHUB_REGISTRY ?= docker.io/gmcintire
IMAGE_NAME ?= elixir-runtime
ERLANG_VERSION ?= 28.3
ELIXIR_VERSION ?= 1.19.5
TAG ?= $(ERLANG_VERSION)-elixir-$(ELIXIR_VERSION)-debian-trixie
BUILDER_IMAGE_NAME ?= elixir-builder
RUNTIME_IMAGE_NAME ?= debian-runtime
ELIXIR_VERSION ?= 1.19.4
OTP_VERSION ?= 28.3
DEBIAN_VERSION ?= trixie-20251229-slim
TAG ?= $(ELIXIR_VERSION)-erlang-$(OTP_VERSION)-debian-$(DEBIAN_VERSION)
help: ## Show this help message
@echo "Available targets:"
@ -20,41 +22,57 @@ build: ## Build the base image with latest packages
update: build ## Alias for build (rebuilds with latest packages)
test: ## Test the built image
@echo "Testing Erlang..."
@$(CONTAINER_CMD) run --rm $(REGISTRY)/$(IMAGE_NAME):latest erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell
@echo "Testing Elixir..."
@$(CONTAINER_CMD) run --rm $(REGISTRY)/$(IMAGE_NAME):latest elixir --version
@echo "Testing locale..."
@$(CONTAINER_CMD) run --rm $(REGISTRY)/$(IMAGE_NAME):latest locale
test: ## Test the built images
@echo "Testing builder image..."
@$(CONTAINER_CMD) run --rm $(REGISTRY)/$(BUILDER_IMAGE_NAME):latest mix --version
@echo "Testing runtime image..."
@$(CONTAINER_CMD) run --rm $(REGISTRY)/$(RUNTIME_IMAGE_NAME):latest locale | grep "en_US.UTF-8"
@echo "✅ All tests passed!"
push: ## Push image to GitLab and Docker Hub
@echo "Pushing to GitLab registry..."
@$(CONTAINER_CMD) push $(REGISTRY)/$(IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) push $(REGISTRY)/$(IMAGE_NAME):latest
push: ## Push images to GitLab and Docker Hub
@echo "Pushing builder image to GitLab registry..."
@$(CONTAINER_CMD) push $(REGISTRY)/$(BUILDER_IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) push $(REGISTRY)/$(BUILDER_IMAGE_NAME):latest
@echo "Pushing runtime image to GitLab registry..."
@$(CONTAINER_CMD) push $(REGISTRY)/$(RUNTIME_IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) push $(REGISTRY)/$(RUNTIME_IMAGE_NAME):latest
@echo "Tagging for Docker Hub..."
@$(CONTAINER_CMD) tag $(REGISTRY)/$(IMAGE_NAME):$(TAG) $(DOCKERHUB_REGISTRY)/$(IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) tag $(REGISTRY)/$(IMAGE_NAME):latest $(DOCKERHUB_REGISTRY)/$(IMAGE_NAME):latest
@$(CONTAINER_CMD) tag $(REGISTRY)/$(BUILDER_IMAGE_NAME):$(TAG) $(DOCKERHUB_REGISTRY)/$(BUILDER_IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) tag $(REGISTRY)/$(BUILDER_IMAGE_NAME):latest $(DOCKERHUB_REGISTRY)/$(BUILDER_IMAGE_NAME):latest
@$(CONTAINER_CMD) tag $(REGISTRY)/$(RUNTIME_IMAGE_NAME):$(TAG) $(DOCKERHUB_REGISTRY)/$(RUNTIME_IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) tag $(REGISTRY)/$(RUNTIME_IMAGE_NAME):latest $(DOCKERHUB_REGISTRY)/$(RUNTIME_IMAGE_NAME):latest
@echo "Pushing to Docker Hub..."
@$(CONTAINER_CMD) push $(DOCKERHUB_REGISTRY)/$(IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) push $(DOCKERHUB_REGISTRY)/$(IMAGE_NAME):latest
@$(CONTAINER_CMD) push $(DOCKERHUB_REGISTRY)/$(BUILDER_IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) push $(DOCKERHUB_REGISTRY)/$(BUILDER_IMAGE_NAME):latest
@$(CONTAINER_CMD) push $(DOCKERHUB_REGISTRY)/$(RUNTIME_IMAGE_NAME):$(TAG)
@$(CONTAINER_CMD) push $(DOCKERHUB_REGISTRY)/$(RUNTIME_IMAGE_NAME):latest
pull: ## Pull latest image from registry
@$(CONTAINER_CMD) pull $(REGISTRY)/$(IMAGE_NAME):latest
pull: ## Pull latest images from registry
@$(CONTAINER_CMD) pull $(REGISTRY)/$(BUILDER_IMAGE_NAME):latest
@$(CONTAINER_CMD) pull $(REGISTRY)/$(RUNTIME_IMAGE_NAME):latest
inspect: ## Show image details (size, layers, labels)
@echo "Image size:"
@$(CONTAINER_CMD) images $(REGISTRY)/$(IMAGE_NAME):latest --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
@echo "Builder image size:"
@$(CONTAINER_CMD) images $(REGISTRY)/$(BUILDER_IMAGE_NAME):latest --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
@echo ""
@echo "Image labels:"
@$(CONTAINER_CMD) inspect $(REGISTRY)/$(IMAGE_NAME):latest --format '{{range $$k, $$v := .Config.Labels}}{{$$k}}={{$$v}}{{println}}{{end}}'
@echo "Runtime image size:"
@$(CONTAINER_CMD) images $(REGISTRY)/$(RUNTIME_IMAGE_NAME):latest --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
@echo ""
@echo "Builder image labels:"
@$(CONTAINER_CMD) inspect $(REGISTRY)/$(BUILDER_IMAGE_NAME):latest --format '{{range $$k, $$v := .Config.Labels}}{{$$k}}={{$$v}}{{println}}{{end}}'
@echo ""
@echo "Runtime image labels:"
@$(CONTAINER_CMD) inspect $(REGISTRY)/$(RUNTIME_IMAGE_NAME):latest --format '{{range $$k, $$v := .Config.Labels}}{{$$k}}={{$$v}}{{println}}{{end}}'
clean: ## Remove local images
@$(CONTAINER_CMD) rmi $(REGISTRY)/$(IMAGE_NAME):$(TAG) || true
@$(CONTAINER_CMD) rmi $(REGISTRY)/$(IMAGE_NAME):latest || true
@$(CONTAINER_CMD) rmi $(DOCKERHUB_REGISTRY)/$(IMAGE_NAME):$(TAG) || true
@$(CONTAINER_CMD) rmi $(DOCKERHUB_REGISTRY)/$(IMAGE_NAME):latest || true
@$(CONTAINER_CMD) rmi $(REGISTRY)/$(BUILDER_IMAGE_NAME):$(TAG) || true
@$(CONTAINER_CMD) rmi $(REGISTRY)/$(BUILDER_IMAGE_NAME):latest || true
@$(CONTAINER_CMD) rmi $(REGISTRY)/$(RUNTIME_IMAGE_NAME):$(TAG) || true
@$(CONTAINER_CMD) rmi $(REGISTRY)/$(RUNTIME_IMAGE_NAME):latest || true
@$(CONTAINER_CMD) rmi $(DOCKERHUB_REGISTRY)/$(BUILDER_IMAGE_NAME):$(TAG) || true
@$(CONTAINER_CMD) rmi $(DOCKERHUB_REGISTRY)/$(BUILDER_IMAGE_NAME):latest || true
@$(CONTAINER_CMD) rmi $(DOCKERHUB_REGISTRY)/$(RUNTIME_IMAGE_NAME):$(TAG) || true
@$(CONTAINER_CMD) rmi $(DOCKERHUB_REGISTRY)/$(RUNTIME_IMAGE_NAME):latest || true
@echo "✅ Cleaned local images"
login: ## Login to GitLab container registry

View file

@ -16,107 +16,149 @@ fi
# Configuration
REGISTRY="${REGISTRY:-registry.gitlab.com/towerops/towerops}"
IMAGE_NAME="${IMAGE_NAME:-elixir-runtime}"
ERLANG_VERSION="${ERLANG_VERSION:-28.3}"
ELIXIR_VERSION="${ELIXIR_VERSION:-1.19.5}"
ELIXIR_VERSION="${ELIXIR_VERSION:-1.19.4}"
OTP_VERSION="${OTP_VERSION:-28.3}"
DEBIAN_VERSION="${DEBIAN_VERSION:-trixie-20251229-slim}"
# Build timestamp for tracking
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
GIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
# Image names
BUILDER_IMAGE_NAME="elixir-builder"
RUNTIME_IMAGE_NAME="debian-runtime"
# Image tags
TAG_FULL="${ERLANG_VERSION}-elixir-${ELIXIR_VERSION}-debian-trixie"
TAG_FULL="${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
TAG_LATEST="latest"
TAG_DATED="${TAG_FULL}-${BUILD_DATE//[:-]*/}" # YYYY-MM-DD format
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Building Minimal Debian Base Image"
echo "Building Base Images for Towerops"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Container: ${CONTAINER_CMD}"
echo "Registry: ${REGISTRY}"
echo "Image: ${IMAGE_NAME}"
echo "Erlang: ${ERLANG_VERSION}"
echo "Builder Image: ${BUILDER_IMAGE_NAME}"
echo "Runtime Image: ${RUNTIME_IMAGE_NAME}"
echo "Elixir: ${ELIXIR_VERSION}"
echo "Erlang: ${OTP_VERSION}"
echo "Debian: ${DEBIAN_VERSION}"
echo "Build Date: ${BUILD_DATE}"
echo "Git SHA: ${GIT_SHA}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Build the image with no cache to ensure latest packages
# Build builder image (no cache to ensure latest packages)
echo ""
echo "🔨 Building image (no cache, pulling latest packages)..."
echo "🔨 Building builder image (no cache, pulling latest packages)..."
$CONTAINER_CMD build \
--no-cache \
--pull \
--build-arg DEBIAN_VERSION="${DEBIAN_VERSION}" \
--build-arg ERLANG_VERSION="${ERLANG_VERSION}" \
--target builder \
--build-arg ELIXIR_VERSION="${ELIXIR_VERSION}" \
--build-arg OTP_VERSION="${OTP_VERSION}" \
--build-arg DEBIAN_VERSION="${DEBIAN_VERSION}" \
--label "build.date=${BUILD_DATE}" \
--label "build.git-sha=${GIT_SHA}" \
--label "erlang.version=${ERLANG_VERSION}" \
--label "elixir.version=${ELIXIR_VERSION}" \
-t "${REGISTRY}/${IMAGE_NAME}:${TAG_FULL}" \
-t "${REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}" \
-t "${REGISTRY}/${IMAGE_NAME}:${TAG_DATED}" \
--label "erlang.version=${OTP_VERSION}" \
--label "debian.version=${DEBIAN_VERSION}" \
-t "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_FULL}" \
-t "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}" \
-t "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_DATED}" \
-f Dockerfile \
.
# Build runtime image
echo ""
echo "🔨 Building runtime image..."
$CONTAINER_CMD build \
--no-cache \
--pull \
--target runtime \
--build-arg DEBIAN_VERSION="${DEBIAN_VERSION}" \
--label "build.date=${BUILD_DATE}" \
--label "build.git-sha=${GIT_SHA}" \
--label "debian.version=${DEBIAN_VERSION}" \
-t "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_FULL}" \
-t "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}" \
-t "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_DATED}" \
-f Dockerfile \
.
echo ""
echo "✅ Build complete!"
echo ""
echo "Image tags created:"
echo " - ${REGISTRY}/${IMAGE_NAME}:${TAG_FULL}"
echo " - ${REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}"
echo " - ${REGISTRY}/${IMAGE_NAME}:${TAG_DATED}"
echo "Builder image tags:"
echo " - ${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_FULL}"
echo " - ${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}"
echo " - ${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_DATED}"
echo ""
echo "Runtime image tags:"
echo " - ${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_FULL}"
echo " - ${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}"
echo " - ${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_DATED}"
echo ""
# Show image size
echo "📦 Image size:"
$CONTAINER_CMD images "${REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}" --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"
# Show image sizes
echo "📦 Image sizes:"
$CONTAINER_CMD images "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}" --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"
$CONTAINER_CMD images "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}" --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"
echo ""
# Verify image works
echo "🔍 Verifying image..."
$CONTAINER_CMD run --rm "${REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}" erl -eval 'io:format("Erlang/OTP: ~s~n", [erlang:system_info(otp_release)]), halt().' -noshell
$CONTAINER_CMD run --rm "${REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}" elixir --version
# Verify images work
echo "🔍 Verifying builder image..."
$CONTAINER_CMD run --rm "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}" mix --version
echo ""
echo "🔍 Verifying runtime image..."
$CONTAINER_CMD run --rm "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}" locale | grep "en_US.UTF-8"
echo ""
# Ask if user wants to push
read -p "Push to registries? (y/N) " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "🚀 Pushing to GitLab registry..."
$CONTAINER_CMD push "${REGISTRY}/${IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD push "${REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD push "${REGISTRY}/${IMAGE_NAME}:${TAG_DATED}"
echo "🚀 Pushing builder image to GitLab registry..."
$CONTAINER_CMD push "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD push "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD push "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_DATED}"
echo "🚀 Pushing runtime image to GitLab registry..."
$CONTAINER_CMD push "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD push "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD push "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_DATED}"
echo ""
echo "🚀 Tagging for Docker Hub..."
DOCKERHUB_REGISTRY="docker.io/gmcintire"
$CONTAINER_CMD tag "${REGISTRY}/${IMAGE_NAME}:${TAG_FULL}" "${DOCKERHUB_REGISTRY}/${IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD tag "${REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}" "${DOCKERHUB_REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD tag "${REGISTRY}/${IMAGE_NAME}:${TAG_DATED}" "${DOCKERHUB_REGISTRY}/${IMAGE_NAME}:${TAG_DATED}"
$CONTAINER_CMD tag "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_FULL}" "${DOCKERHUB_REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD tag "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}" "${DOCKERHUB_REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD tag "${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_DATED}" "${DOCKERHUB_REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_DATED}"
$CONTAINER_CMD tag "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_FULL}" "${DOCKERHUB_REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD tag "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}" "${DOCKERHUB_REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD tag "${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_DATED}" "${DOCKERHUB_REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_DATED}"
echo "🚀 Pushing to Docker Hub..."
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${IMAGE_NAME}:${TAG_DATED}"
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_DATED}"
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_FULL}"
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}"
$CONTAINER_CMD push "${DOCKERHUB_REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_DATED}"
echo ""
echo "✅ Pushed successfully to both registries!"
echo ""
echo "Update your main Dockerfile to use:"
echo " GitLab: ARG RUNNER_IMAGE=\"${REGISTRY}/${IMAGE_NAME}:${TAG_FULL}\""
echo " Docker Hub: ARG RUNNER_IMAGE=\"${DOCKERHUB_REGISTRY}/${IMAGE_NAME}:${TAG_FULL}\""
echo " Builder: ARG BUILDER_IMAGE=\"${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}\""
echo " Runtime: ARG RUNNER_IMAGE=\"${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}\""
else
echo "⏭️ Skipping push. To push later, run:"
echo " $CONTAINER_CMD push ${REGISTRY}/${IMAGE_NAME}:${TAG_FULL}"
echo " $CONTAINER_CMD push ${REGISTRY}/${IMAGE_NAME}:${TAG_LATEST}"
echo " $CONTAINER_CMD push ${REGISTRY}/${IMAGE_NAME}:${TAG_DATED}"
echo " $CONTAINER_CMD push docker.io/gmcintire/${IMAGE_NAME}:${TAG_FULL}"
echo " $CONTAINER_CMD push docker.io/gmcintire/${IMAGE_NAME}:${TAG_LATEST}"
echo " $CONTAINER_CMD push docker.io/gmcintire/${IMAGE_NAME}:${TAG_DATED}"
echo "⏭️ Skipping push. To push later:"
echo " GitLab:"
echo " $CONTAINER_CMD push ${REGISTRY}/${BUILDER_IMAGE_NAME}:${TAG_LATEST}"
echo " $CONTAINER_CMD push ${REGISTRY}/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}"
echo " Docker Hub:"
echo " $CONTAINER_CMD push docker.io/gmcintire/${BUILDER_IMAGE_NAME}:${TAG_LATEST}"
echo " $CONTAINER_CMD push docker.io/gmcintire/${RUNTIME_IMAGE_NAME}:${TAG_LATEST}"
fi
echo ""