Always use the latest compatible versions of everything, with zero version pins anywhere — while maintaining 100% reproducible builds and failing fast in TEST/DEV.
This is the pattern used internally by Shopify, GitHub, GitLab, and most large Jekyll/Docker teams that want bleeding-edge dependencies with zero version drift.
ruby '3.3', no gem 'jekyll', '~> 4.3', no FROM ruby:3.3, no BUNDLER_VERSION=…):latest)┌─────────────────────────────────────────────────────────────────────────────┐
│ ZERO PIN DEPENDENCY FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. DEVELOP/TEST (Build with --no-cache) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Dockerfile │───▶│ Bundler │───▶│ Gemfile.lock│ │
│ │ (no pins) │ │ resolves │ │ (auto-gen) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Latest compatible versions for ALL gems │ │
│ │ → Tests run against these exact versions │ │
│ │ → If incompatible: BUILD FAILS ❌ │ │
│ │ → If compatible: Tests proceed ✅ │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. CI/TEST PASS │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Create immutable image tag: │ │
│ │ bamr87/zer0-mistakes:20251128-1420-a1b2c3d │ │
│ │ (date + time + commit hash) │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. PRODUCTION (Uses immutable tag) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ image: bamr87/zer0-mistakes:20251128-1420-... │ │
│ │ → Exact same image that passed tests │ │
│ │ → 100% reproducible │ │
│ │ → NEVER uses :latest in production │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
zer0-mistakes/
├── docker/
│ ├── Dockerfile # Multi-stage, zero version pins
│ └── config/
│ ├── production.yml # Jekyll production config
│ └── development.yml # Jekyll development config
├── Gemfile # Zero version constraints
├── Gemfile.lock # Auto-generated, tracks exact versions
├── jekyll-theme-zer0.gemspec # Minimal version requirements
├── docker-compose.yml # Development environment
├── docker-compose.test.yml # CI testing overlay
├── docker-compose.prod.yml # Production (immutable tags only)
└── .github/workflows/
└── test-latest.yml # Fail-fast CI with latest deps
docker/Dockerfile)# Zero version pins - always latest
FROM ruby:slim AS base
# Install dependencies without version pins
RUN apt-get update -qq && \
apt-get install -y build-essential libyaml-dev ...
# Install latest Bundler
RUN gem install bundler
# Let Bundler resolve latest compatible
COPY Gemfile Gemfile.lock* ./
RUN bundle install
source "https://rubygems.org"
gemspec
# NO version constraints → always latest compatible
gem "github-pages", group: :jekyll_plugins
gem "webrick"
gem "ffi"
gem "commonmarker"
services:
jekyll:
# NEVER use :latest in production
# Always use immutable tag from CI
image: bamr87/zer0-mistakes:${IMAGE_TAG:-20251128-1420-a1b2c3d}
# Start development server (builds with latest deps)
docker compose up
# Rebuild with fresh dependencies
docker compose up --build
# Access container shell
docker compose exec jekyll bash
# Build with no cache to ensure latest dependencies
docker compose -f docker-compose.yml -f docker-compose.test.yml build --no-cache
# Run tests
docker compose -f docker-compose.yml -f docker-compose.test.yml run jekyll
# Validate only
docker compose -f docker-compose.yml -f docker-compose.test.yml run validate
# Get the latest successful tag from CI artifact
IMAGE_TAG=$(cat LATEST_SUCCESSFUL_TAG)
# Deploy with immutable tag
IMAGE_TAG=$IMAGE_TAG docker compose -f docker-compose.prod.yml up -d
# Or set in environment file
echo "IMAGE_TAG=$IMAGE_TAG" > .env.prod
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d
| Goal | How It’s Achieved |
|---|---|
| Always use latest dependencies | Zero version pins anywhere |
| Auto-resolve compatible set | Bundler + lockfile does it on every build |
| Incompatibilities caught early | Build fails loudly in TEST/CI → PR blocked |
| Production never breaks | Only images that passed TEST are promoted |
| Zero version maintenance | No one ever has to bump versions manually |
| Full reproducibility | Immutable tags (date+commit) lock the exact set |
When the CI build fails due to an upstream breaking change:
debug-failure job shows exactly what versions were attempted# Gemfile
# TEMPORARY: commonmarker 0.24.0 breaks our build
# Issue: https://github.com/github/commonmarker/issues/XXX
# TODO: Remove this pin when issue is resolved
gem "commonmarker", "< 0.24.0"
The .github/workflows/test-latest.yml workflow:
--no-cache (latest everything)If you’re coming from a project with version pins:
GemfilegemspecDockerfile to use ruby:slim (no version)Gemfile.lock (let it regenerate)docker compose up --buildGemfile.lock