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¶
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.
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¶
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¶
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¶
Why bad? - Increases complexity - Harder to reason about permissions - If your role needs specific users, let it create them
❌ Don't Install Security Hardening¶
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?
- Build Test: Does the Containerfile build successfully?
- SSH Test: Can we SSH with the injected key?
- systemd Test: Do services start/stop correctly?
- Ansible Test: Can Ansible connect and run commands?
- 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.