Skip to content

Design Philosophy

Core Principle: Factory Defaults

Keep it minimal. Stock OS. Testing only.

These containers exist for one purpose: reliable, repeatable Ansible role testing across multiple Linux distributions. Every design decision supports this goal.

What Minimal Means

Included: Testing Essentials

Base OS + Python + SSH + systemd = Testing Container

That's it. Everything else is extra.

  • Python 3: Ansible requires it
  • OpenSSH: Ansible connects via SSH
  • systemd: Most roles test service management
  • Factory OS: Stock configuration like a fresh server

Excluded: Everything Else

These containers deliberately omit:

  • ❌ Configuration management beyond Ansible
  • ❌ Monitoring agents (Prometheus, Telegraf, etc.)
  • ❌ Application stacks (LAMP, LEMP, etc.)
  • ❌ Container orchestration tools
  • ❌ Custom security hardening
  • ❌ Performance tuning
  • ❌ Additional users beyond jackaltx
  • ❌ GUI tools or X11

Why exclude? Because Ansible roles should install and configure these things. If the base image has them pre-installed, you're not actually testing your role.

Design Decisions

SSH Key Authentication Only

No passwords. SSH keys only.

# Inject key during build
ARG SSH_KEY
RUN echo "$SSH_KEY" > /home/jackaltx/.ssh/authorized_keys

Why? - Matches real infrastructure (key-based auth is standard) - More secure than hardcoded passwords - Easy to customize per user/environment - Supports CI/CD workflows

Passwordless Sudo

RUN echo "jackaltx ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/jackaltx

Why? - Ansible testing requires privilege escalation - Interactive password prompts break automation - This is a testing environment only - not production

Security Note: These containers are NOT suitable for production use. The passwordless sudo is a deliberate trade-off for testing convenience.

systemd as Init

podman run -d \
    --privileged \
    -v /sys/fs/cgroup:/sys/fs/cgroup:rw \
    container-image \
    /sbin/init  # <-- systemd

Why? - Most Ansible roles manage services via systemd - Testing service start/stop/restart requires systemd - Matches production Linux servers

Trade-off: Requires --privileged mode and cgroup mounts.

Stock OS Configuration

Each container starts with distribution defaults:

  • Default package repositories
  • Default configurations (/etc/)
  • Standard file system layout
  • Distribution-standard kernel parameters

Why? - Tests how roles behave on fresh servers - Avoids hidden assumptions from custom configs - Ensures portability across environments

What gets updated: - Security updates (apt-get update && apt-get upgrade) - But NO configuration changes

Single User: jackaltx

RUN useradd -m -s /bin/bash jackaltx

Only one non-root user. Period.

Why? - Simple and predictable - Matches typical provisioning user - Reduces complexity - Easy to understand and debug

Minimal Utilities

RUN apt-get install -y \
    python3 \
    openssh-server \
    systemd \
    sudo \
    vim \      # <-- debugging
    wget \     # <-- fetching files
    git \      # <-- cloning roles
    tmux       # <-- session management

Why these specific utilities? - vim: Edit files during debugging - wget: Fetch files in test scenarios - git: Clone roles for testing - tmux: Manage multiple shell sessions

What's missing? - No nano, emacs, joe - pick one editor (vim) - No curl - wget is sufficient - No htop, nethogs, etc. - not needed for testing

Anti-Patterns to Avoid

❌ Don't Add "Convenience" Features

# BAD - Don't do this
RUN apt-get install -y \
    docker.io \           # Testing Docker roles? Test installation!
    nginx \               # Testing web server? Let the role install it!
    postgresql-client     # Testing DB access? Install it in your role!

Why bad? - Hides whether your role correctly installs dependencies - Creates false positives in tests - Diverges from production environments

❌ Don't Customize Configurations

# BAD - Don't do this
RUN echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
RUN sed -i 's/#PermitRootLogin/PermitRootLogin no/' /etc/ssh/sshd_config

Why bad? - Ansible roles should configure these settings - Testing loses value if configs are pre-applied - Harder to troubleshoot role failures

❌ Don't Add Multiple Users

# BAD - Don't do this
RUN useradd -m ansible && \
    useradd -m deploy && \
    useradd -m app

Why bad? - Increases complexity - Harder to reason about permissions - If your role needs specific users, let it create them

❌ Don't Install Security Hardening

# BAD - Don't do this
RUN apt-get install -y fail2ban aide rkhunter
RUN systemctl enable fail2ban

Why bad? - Security roles should test hardening installation - Pre-hardened images hide role effectiveness - Different security requirements per use case

Comparison with Alternatives

vs Base Distribution Images

# Official images require manual setup
podman run -d debian:12 /bin/bash
# No SSH, no systemd, no user configured

# Testing containers are ready immediately
podman run -d ghcr.io/jackaltx/testing-containers/debian-ssh:12 /sbin/init
# SSH running, systemd active, user ready

vs "Kitchen" Images

Chef Test Kitchen images often include: - Pre-installed Ruby - Chef client - Multiple shells - Additional debugging tools

Testing Containers: Just what Ansible needs. Nothing more.

vs "Golden" Images

Some teams build "golden images" with: - Pre-hardened configs - Monitoring pre-installed - Custom package sets

Testing Containers: Factory defaults only. Test from ground zero.

When to Deviate

Never Deviate: Core Principles

These are inviolable: - ✅ SSH key authentication - ✅ Factory OS defaults - ✅ Single user (jackaltx) - ✅ Minimal package set

Rarely Deviate: Utility Packages

Add utilities only if: 1. Required by >50% of Ansible roles 2. Not installable via role (circular dependency) 3. Debugging is impossible without them

Example: iproute2 was added because network debugging was too difficult without ip command.

Sometimes Deviate: Distribution-Specific

Some distros need small tweaks:

# Rocky Linux: Enable EPEL for some packages
# Debian: Non-interactive frontend for apt
# Ubuntu: Might need universe/multiverse repos

Rule: Only deviate when distribution's own documentation recommends it.

Testing the Test Containers

Meta-testing: How do we know these containers work?

  1. Build Test: Does the Containerfile build successfully?
  2. SSH Test: Can we SSH with the injected key?
  3. systemd Test: Do services start/stop correctly?
  4. Ansible Test: Can Ansible connect and run commands?
  5. Molecule Test: Does a simple role deploy successfully?

See Building Images for testing procedures.

Maintenance Philosophy

Updates: Security Only

Images are rebuilt monthly with: - ✅ OS security updates (apt-get upgrade) - ✅ Package repository updates - ❌ NO feature additions - ❌ NO configuration changes - ❌ NO new utilities (unless critical need emerges)

Versioning: Distribution Version

# Version tracks OS version
debian-ssh:12    # Debian 12
rocky-ssh:9      # Rocky Linux 9
ubuntu-ssh:24    # Ubuntu 24.04

No semantic versioning - these track upstream OS releases.

Deprecation: Follow Upstream

Images are deprecated when: - Upstream OS reaches end-of-life - Distribution drops support - Community stops using version

Example: When Debian 12 reaches EOL, debian-ssh:12 will be deprecated and debian-ssh:13 will be the new target.

Why This Matters

Reliable Tests

Minimal, predictable containers mean: - Tests pass/fail for the right reasons - Fewer false positives from environment quirks - Reproducible results across team members

Fast Iteration

Small images with few dependencies: - Pull faster from registry - Start faster (less to initialize) - Rebuild faster when needed

Clear Responsibility

If something fails: - Not the container's fault (it's minimal) - Must be the role or the test - Easier to debug and fix

Summary

Testing Containers are deliberately simple by design. Every missing feature is intentional. Every included component serves testing. This minimalism is the feature, not a limitation.

When tempted to add something, ask: 1. Does Ansible require it? 2. Would including it hide role defects? 3. Is it truly essential for testing?

If all three answers aren't "yes", don't add it.