Skip to content

Architecture

Infrashift DevContainer Features follow a layered architecture: a base Containerfile provides the foundation, and each feature uses a standardized install pipeline of shell → Ansible → tool installation.

┌─────────────────────────────────┐
│ Red Hat UBI 9 (ubi:latest) │
├─────────────────────────────────┤
│ dnf update │
│ Create vscode user (1000:1000) │
│ Install UV bootstrapper │
└─────────────────────────────────┘

The base Containerfile (.devcontainer/Containerfile) starts from UBI9, creates the vscode user, and installs UV as the sole bootstrap dependency. UV enables Ansible execution without permanently installing ansible-core.

Each feature follows the same three-stage pipeline:

install.sh → uv run ansible-playbook → activate-feature.yml

The install.sh script runs as root during container build. It’s a thin wrapper that:

  1. Validates that UV is available
  2. Reads feature options from environment variables (set by the Dev Container runtime)
  3. Invokes uv run --with ansible-core ansible-playbook with feature-specific extra variables

UV creates a temporary virtual environment, installs ansible-core into it, and runs the playbook. This avoids permanently installing ansible-core in the image.

The activate-feature.yml playbook contains the actual installation logic:

  • Download binaries from official sources
  • Verify SHA256 checksums (when provided)
  • Extract and install to user-scoped directories
  • Set file permissions for the vscode user
src/
├── <feature-id>/
│ ├── devcontainer-feature.json # Feature metadata and options
│ ├── install.sh # Entry point (called by runtime)
│ ├── activate-feature.yml # Ansible playbook
│ └── hosts.yml # Ansible inventory (localhost)
test/
├── <feature-id>/
│ ├── test.sh # Autogenerated test script
│ └── scenarios.json # Scenario test definitions

Features install software into user-scoped directories under /home/vscode/:

PathPurpose
~/.local/binCLI binaries (jq, yq, grype, syft, etc.)
~/.local/share/goGo installation
~/.local/share/nodejsNode.js installation
~/.local/share/javaOpenJDK installation
~/.local/share/dotnet.NET SDK installation
~/.local/share/pnpmpnpm home directory
~/.local/share/gopathGo workspace (GOPATH)
~/.bun/binBun global installs (Claude Code, OpenAI Codex)
bun ──→ claude-code
└─→ openai-codex
nodejs ──→ npm
└──→ pnpm
golang ──→ cuelang
uv-ruff ──→ python ──→ ansible-core
git ──→ git-lfs

Features use installsAfter declarations to ensure correct installation order. The Dev Container runtime resolves the dependency graph automatically.

The test pipeline (.github/workflows/test-all.yaml) runs three test stages:

  1. Autogenerated tests: Each feature is tested in isolation against a UBI9 base image
  2. Scenario tests: Features with dependencies are tested in combination
  3. Global integration: All features are tested together in a single container

Each test stage builds the UBI9 base image from the Containerfile, then uses devcontainers/ci to run the tests.