Architecture

Generation Pipeline

manifest.yml is the single source of truth for all image definitions.

manifest.yml
  └─▶ lib/image_generator.rb (ERB rendering)
       ├─▶ core/noble/Dockerfile
       ├─▶ core/noble/docker-bake.hcl
       ├─▶ ruby/4.0/Dockerfile
       └─▶ node/24/Dockerfile

Image Hierarchy

ubuntu:noble / ubuntu:jammy / ubuntu:bionic
  └─▶ core (base packages, users, locale)
       ├─▶ core-dev (build-essential, dev headers)
       ├─▶ ruby (Ruby + Bundler + RubyGems)
       │    └─▶ ruby-dev
       └─▶ node (Node.js + npm + Yarn)
            └─▶ node-dev

Manifest Structure

globals:
  defaults:           # Apply to all image types
    registry: ghcr.io/djbender
    platforms: [linux/amd64, linux/arm64]

core:
  versions:
    noble:
      latest: true    # Gets the :latest tag

ruby:
  defaults:           # Override globals for ruby
    base_image: "%{registry}/core"
  versions:
    '4.0':
      ruby_version: 4.0.1
      latest: true

Tag Generation

lib/tag_generator.rb generates tags consistently across templates and CI:

CI Flow

push to main
  └─▶ GitHub Actions (build-images.yml)
       ├─▶ RSpec + RuboCop
       ├─▶ Build matrix (per image × per platform)
       │    ├─▶ linux/amd64 (ubuntu-latest)
       │    └─▶ linux/arm64 (ubuntu-24.04-arm)
       └─▶ Merge jobs (multi-arch manifests)
            └─▶ Push to ghcr.io/djbender/*

Feature branches build single-arch only. Multi-arch builds run on push to main.