Thank you for your interest in contributing to ContainAI! This guide covers development setup, coding conventions, testing, and the pull request process.
- Getting Started
- Development Environment
- Coding Conventions
- Testing
- Pull Request Process
- Good First Issues
- Architecture Overview
- Fork the repository on GitHub
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/containai.git cd containai - Create a feature branch:
git checkout -b feature/your-feature-name
- Bash 4.0+ - The CLI requires bash (not zsh or fish)
- Docker Desktop 4.50+ with sandbox feature enabled, OR
- Sysbox runtime installed (for Linux/WSL2/macOS via Lima)
- Git for version control
ContainAI scripts require bash. If your default shell is zsh or fish:
# Switch to bash before developing
bash
# Or run bash explicitly
bash ./tests/integration/test-secure-engine.sh# Source the CLI to load all functions
source src/containai.sh
# Verify your environment
cai doctorContainAI builds use Docker buildx by default to match CI behavior. The default platform is linux/<host-arch> even on macOS (builds run inside Lima). Use --build-setup to configure a buildx builder and binfmt if required.
# Build all layers for the current host arch (buildx + --load)
./src/build.sh
# Configure buildx builder/binfmt (first-time setup)
./src/build.sh --build-setup
# Build and tag for a registry (all layers)
./src/build.sh --image-prefix ghcr.io/ORG/containai --platforms linux/amd64,linux/arm64 --push --build-setup
# Multi-arch build (CI style) - requires --push or --output
./src/build.sh --platforms linux/amd64,linux/arm64 --push --build-setupcontainai/
├── src/ # Main CLI and container runtime
│ ├── containai.sh # Entry point (sources lib/*.sh)
│ ├── lib/ # Modular shell libraries
│ │ ├── core.sh # Logging utilities
│ │ ├── platform.sh # OS detection
│ │ ├── docker.sh # Docker helpers
│ │ ├── eci.sh # ECI detection
│ │ ├── doctor.sh # Health checks
│ │ ├── config.sh # TOML parsing
│ │ ├── container.sh # Container lifecycle
│ │ ├── import.sh # Dotfile sync
│ │ ├── export.sh # Volume backup
│ │ ├── setup.sh # Sysbox installation
│ │ └── env.sh # Environment handling
│ └── container/ # Container-specific content
│ ├── entrypoint.sh # Container entrypoint (security validation)
│ └── Dockerfile* # Container image definitions
├── tests/ # Test suites
│ ├── unit/ # Unit tests (portable)
│ └── integration/ # Integration tests (require Docker)
├── docs/ # Documentation
├── SECURITY.md # Security model
└── README.md # Project overview
See docs/architecture.md for detailed component documentation.
ContainAI follows strict shell scripting conventions for portability and safety.
# Good
if command -v docker >/dev/null 2>&1; then
printf '%s\n' "Docker found"
fi
# Bad - 'which' is not a shell builtin and may not exist
if which docker >/dev/null 2>&1; then
printf '%s\n' "Docker found"
fi# Good - handles all strings safely
printf '%s\n' "Message: $var"
printf '%s\n' "-n this is not a flag"
# Bad - echo mishandles strings starting with -n/-e
echo "Message: $var"
echo "-n this looks like a flag"# Good - consistent ASCII markers
printf '%s\n' "[OK] Operation succeeded"
printf '%s\n' "[WARN] Non-critical issue"
printf '%s\n' "[ERROR] Operation failed"
# Bad - inconsistent formats
echo "OK: Operation succeeded"
echo "WARNING - Non-critical issue"
echo "Error: Operation failed"In sourced scripts, loop variables pollute the caller's environment:
# Good - prevents shell pollution
my_function() {
local item
for item in "$@"; do
process "$item"
done
}
# Bad - 'item' leaks to caller's environment
my_function() {
for item in "$@"; do
process "$item"
done
}# Good - POSIX compatible
grep -E '[[:space:]]+'
# Bad - ERE does not support \s
grep -E '\s+'# Good - captures exit code correctly
if ! result=$(some_command); then
printf '%s\n' "[ERROR] Command failed" >&2
return 1
fi
# Bad - dead code under set -e
result=$(some_command)
rc=$? # Never reached if command failsFor the complete list of coding conventions, see .flow/memory/conventions.md.
Common pitfalls to avoid are documented in .flow/memory/pitfalls.md.
Integration tests are located in tests/integration/:
| Script | Purpose |
|---|---|
test-secure-engine.sh |
Verifies Sysbox runtime and Docker context setup |
test-sync-integration.sh |
Tests dotfile sync, config parsing, container lifecycle |
Before submitting docs changes, validate internal links:
./scripts/check-doc-links.shThis script validates all internal markdown links (relative paths and anchors) in docs/ and root markdown files. It catches broken links, invalid anchors, and handles GitHub's duplicate heading behavior.
# Run from the repo root
cd containai
# Run secure engine tests
./tests/integration/test-secure-engine.sh
# Run sync integration tests (requires Docker)
./tests/integration/test-sync-integration.shTests use consistent markers for results:
=== Test Section Name ===
[PASS] Test description
[FAIL] Test description (with remediation hint)
[WARN] Non-critical issue
[INFO] Informational message
When adding new tests:
- Use the standard markers:
[PASS],[FAIL],[WARN],[INFO] - Test actual behavior: Verify sentinel values or specific outputs, not just that operations succeed
- Provide remediation hints: On failure, tell the user how to fix it
- Be hermetic: Clear external env vars with
env -uto avoid test pollution
Example test structure:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/containai.sh"
# Test helpers
pass() { printf '%s\n' "[PASS] $*"; }
fail() { printf '%s\n' "[FAIL] $*" >&2; FAILED=1; }
warn() { printf '%s\n' "[WARN] $*"; }
info() { printf '%s\n' "[INFO] $*"; }
section() { printf '\n%s\n' "=== $* ==="; }
FAILED=0
# Tests
section "Feature X"
if some_condition; then
pass "Feature X works as expected"
else
fail "Feature X failed"
info " Remediation: Check Y and Z"
fi
exit $FAILED-
Run tests to ensure nothing is broken:
./tests/integration/test-secure-engine.sh ./tests/integration/test-sync-integration.sh
-
Follow coding conventions described above
-
Keep changes focused - one feature or fix per PR
-
Update documentation if your change affects user-facing behavior
Use Conventional Commits format:
type(scope): description
- detail 1
- detail 2
Types:
feat: New featurefix: Bug fixdocs: Documentation onlytest: Test changesrefactor: Code change that neither fixes a bug nor adds a featurechore: Maintenance tasks
Examples:
feat(container): add port forwarding support
fix(config): handle TOML arrays correctly
docs(quickstart): add WSL2 setup instructions
test(sync): add credential isolation test
- Create a PR against the
mainbranch - Fill in the PR template with:
- Summary of changes
- Test plan (how you verified the change)
- Related issues (if any)
- Address review feedback promptly
- Squash commits if requested (keep history clean)
Reviewers will check for:
- Correctness: Does the code do what it claims?
- Security: No new attack vectors (this is a sandboxing tool)
- Conventions: Follows shell scripting rules above
- Tests: New features should have tests
- Documentation: User-facing changes need doc updates
Looking for a place to start? Search for issues labeled good first issue.
Good first contributions include:
- Documentation improvements
- Test coverage for existing features
- Bug fixes with clear reproduction steps
- Small enhancements with limited scope
Tips for newcomers:
- Read the architecture docs first: docs/architecture.md
- Understand the security model: SECURITY.md
- Start small - a docs fix or test addition is a great first PR
- Ask questions - open an issue if something is unclear
For a comprehensive understanding of the codebase:
- Architecture Overview - System components, data flow, and design decisions
- Configuration Reference - TOML config schema and semantics
- Technical README - Image building and container internals
Key concepts:
- Dual isolation paths: Docker Desktop sandbox (ECI) or Sysbox runtime
- Modular libraries:
src/lib/*.shmodules with explicit dependencies - Safe defaults: Dangerous operations require explicit CLI flags
- Workspace-scoped config: Per-project settings via TOML config files
- Security issues: See SECURITY.md for responsible disclosure
- Bugs and features: Open a GitHub issue
- General questions: Start a discussion on GitHub Discussions
Thank you for contributing to ContainAI!