* restart: Match Docker Compose semantics Change `cf restart` from doing `down + up` to using `docker compose restart`, matching the Docker Compose command behavior. This provides command naming parity with Docker Compose. Users who want the old behavior can use `cf down mystack && cf up mystack`. - Update restart implementation to use `docker compose restart` - Remove traefik regeneration from restart (no longer recreates containers) - Update all documentation and help text - Remove restart from self-update SSH handling (no longer involves down) * web: Clarify Update tooltip uses 'recreate' not 'restart' Avoid confusion now that 'restart' means something different. * web: Fix Update All tooltip to use 'recreates'
7.0 KiB
Compose Farm Development Guidelines
Core Principles
- KISS: Keep it simple. This is a thin wrapper around
docker composeover SSH. - YAGNI: Don't add features until they're needed. No orchestration, no service discovery, no health checks.
- DRY: Reuse patterns. Common CLI options are defined once, SSH logic is centralized.
Architecture
src/compose_farm/
├── cli/ # CLI subpackage
│ ├── __init__.py # Imports modules to trigger command registration
│ ├── app.py # Shared Typer app instance, version callback
│ ├── common.py # Shared helpers, options, progress bar utilities
│ ├── config.py # Config subcommand (init, show, path, validate, edit, symlink)
│ ├── lifecycle.py # up, down, stop, pull, restart, update, apply, compose commands
│ ├── management.py # refresh, check, init-network, traefik-file commands
│ ├── monitoring.py # logs, ps, stats commands
│ ├── ssh.py # SSH key management (setup, status, keygen)
│ └── web.py # Web UI server command
├── config.py # Pydantic models, YAML loading
├── compose.py # Compose file parsing (.env, ports, volumes, networks)
├── console.py # Shared Rich console instances
├── executor.py # SSH/local command execution, streaming output
├── operations.py # Business logic (up, migrate, discover, preflight checks)
├── state.py # Deployment state tracking (which stack on which host)
├── logs.py # Image digest snapshots (dockerfarm-log.toml)
├── paths.py # Path utilities, config file discovery
├── ssh_keys.py # SSH key path constants and utilities
├── traefik.py # Traefik file-provider config generation from labels
└── web/ # Web UI (FastAPI + HTMX)
Web UI Icons
Icons use Lucide. Add new icons as macros in web/templates/partials/icons.html by copying SVG paths from their site. The action_btn, stat_card, and collapse macros in components.html accept an optional icon parameter.
HTMX Patterns
- Multi-element refresh: Use custom events, not
hx-swap-oob. Elements havehx-trigger="cf:refresh from:body"and JS callsdocument.body.dispatchEvent(new CustomEvent('cf:refresh')). Simpler to debug/test. - SPA navigation: Sidebar uses
hx-boost="true"to AJAX-ify links. - Attribute inheritance: Set
hx-target/hx-swapon parent elements.
Key Design Decisions
- Hybrid SSH approach: asyncssh for parallel streaming with prefixes; native
ssh -tfor raw mode (progress bars) - Parallel by default: Multiple stacks run concurrently via
asyncio.gather - Streaming output: Real-time stdout/stderr with
[stack]prefix using Rich - SSH key auth only: Uses ssh-agent, no password handling (YAGNI)
- NFS assumption: Compose files at same path on all hosts
- Local IP auto-detection: Skips SSH when target host matches local machine's IP
- State tracking: Tracks where stacks are deployed for auto-migration
- Pre-flight checks: Verifies NFS mounts and Docker networks exist before starting/migrating
Code Style
- Imports at top level: Never add imports inside functions unless they are explicitly marked with
# noqa: PLC0415and a comment explaining it speeds up CLI startup. Heavy modules likepydantic,yaml, andrich.tableare lazily imported to keepcf --helpfast.
Development Commands
Use just for common tasks. Run just to list available commands:
| Command | Description |
|---|---|
just install |
Install dev dependencies |
just test |
Run all tests |
just test-cli |
Run CLI tests (parallel) |
just test-web |
Run web UI tests (parallel) |
just lint |
Lint, format, and type check |
just web |
Start web UI (port 9001) |
just doc |
Build and serve docs (port 9002) |
just clean |
Clean build artifacts |
Testing
Run tests with just test or uv run pytest. Browser tests require Chromium (system-installed or via playwright install chromium):
# Unit tests only (parallel)
uv run pytest -m "not browser" -n auto
# Browser tests only (parallel)
uv run pytest -m browser -n auto
# All tests
uv run pytest
Browser tests are marked with @pytest.mark.browser. They use Playwright to test HTMX behavior, JavaScript functionality (sidebar filter, command palette, terminals), and content stability during navigation.
Communication Notes
- Clarify ambiguous wording (e.g., homophones like "right"/"write", "their"/"there").
Git Safety
- Never amend commits.
- NEVER merge anything into main. Always commit directly or use fast-forward/rebase.
- Never force push.
Pull Requests
- Never include unchecked checklists (e.g.,
- [ ] ...) in PR descriptions. Either omit the checklist or use checked items. - NEVER run
gh pr merge. PRs are merged via the GitHub UI, not the CLI.
Releases
Use gh release create to create releases. The tag is created automatically.
# IMPORTANT: Ensure you're on latest origin/main before releasing!
git fetch origin
git checkout origin/main
# Check current version
git tag --sort=-v:refname | head -1
# Create release (minor version bump: v0.21.1 -> v0.22.0)
gh release create v0.22.0 --title "v0.22.0" --notes "release notes here"
Versioning:
- Patch (v0.21.0 → v0.21.1): Bug fixes
- Minor (v0.21.1 → v0.22.0): New features, non-breaking changes
Write release notes manually describing what changed. Group by features and bug fixes.
Commands Quick Reference
CLI available as cf or compose-farm.
| Command | Description |
|---|---|
up |
Start stacks (docker compose up -d), auto-migrates if host changed |
down |
Stop stacks (docker compose down). Use --orphaned to stop stacks removed from config |
stop |
Stop services without removing containers (docker compose stop) |
pull |
Pull latest images |
restart |
Restart running containers (docker compose restart) |
update |
Pull, build, recreate only if changed (up -d --pull always --build) |
apply |
Make reality match config: migrate stacks + stop orphans. Use --dry-run to preview |
compose |
Run any docker compose command on a stack (passthrough) |
logs |
Show stack logs |
ps |
Show status of all stacks |
stats |
Show overview (hosts, stacks, pending migrations; --live for container counts) |
refresh |
Update state from reality: discover running stacks, capture image digests |
check |
Validate config, traefik labels, mounts, networks; show host compatibility |
init-network |
Create Docker network on hosts with consistent subnet/gateway |
traefik-file |
Generate Traefik file-provider config from compose labels |
config |
Manage config files (init, init-env, show, path, validate, edit, symlink) |
ssh |
Manage SSH keys (setup, status, keygen) |
web |
Start web UI server |