# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build and Test Commands
```bash
cargo build # Build the project
cargo test # Run all tests
cargo test <test_name> # Run a single test
cargo clippy # Lint (all warnings are denied)
cargo fmt --check # Check formatting
```
## Project Overview
Flyboat is a container environment manager for development. It wraps Docker/Podman to provide consistent, reproducible development environments with security-first defaults.
**Key concepts:**
- **Environments** live in `~/.flyboat/env/` with a `Dockerfile` and `dev_env.yaml` (supports nested tree structure)
- **Global config** at `~/.flyboat/config.yaml` sets container engine and default arch
- Supports both Docker and Podman (auto-detects, prefers Podman for rootless)
- Container naming: `flyboat-<env>[-<arch>]-<postfix>`
- Image naming: `flyboat-<env>[-<arch>]`
## CLI Commands
```bash
flyboat run <env> # Build and start container
flyboat build <env> # Build container image only
flyboat connect <env> # Connect to existing container
flyboat list [--json] # List available environments
flyboat status # Show disk usage and running containers
flyboat clean # Remove flyboat images and dangling containers
```
**Global flags:** `-v, --verbose` for verbose output
## Environment Structure
Environments can be organized in a nested tree structure:
```
~/.flyboat/env/
├── rust/ # Root-level env: name="rust", full_name="rust"
│ ├── Dockerfile
│ └── dev_env.yaml
├── my_collection/ # Namespace folder (no Dockerfile = ignored)
│ ├── python/ # Nested env: name="python", full_name="my_collection/python"
│ │ ├── Dockerfile
│ │ └── dev_env.yaml
│ └── rust/ # Nested env: name="rust", full_name="my_collection/rust"
│ ├── Dockerfile
│ └── dev_env.yaml
└── tools/web/node/ # Deep nesting: full_name="tools/web/node"
├── Dockerfile
└── dev_env.yaml
```
**Key behaviors:**
- A folder is a valid environment if it has BOTH `Dockerfile` AND `dev_env.yaml`
- Folders without these files are treated as namespace folders (ignored)
- Containers can have subfolders (nested containers are allowed)
- Namespace delimiter: `/` (configurable via `NAMESPACE_DELIMITER` constant in `environment.rs`)
- `Environment.namespace` is `Vec<String>` (e.g., `["my_collection"]` or `["a", "b", "c"]`)
- `Environment.full_name()` method computes the full name from namespace + name
**Example:**
```bash
flyboat run rust # Works if only one "rust" exists
flyboat run my_collection/rust # Always works for that specific env
# If ambiguous: prints "Ambiguous name used, multiple matches where found:" with options
```
## Architecture
```
src/
├── main.rs # CLI entry, command dispatch
├── lib.rs # Public exports, EnvFeaturizedName, sanitize_name(), is_valid_name()
├── cli.rs # clap definitions (Cli, Command, *Args structs)
├── error.rs # Error enum with thiserror
├── environment.rs # EnvironmentManager - discovers and resolves environments
├── config/
│ ├── mod.rs # Module exports
│ ├── paths.rs # Paths struct (~/.flyboat/*)
│ ├── dev_env.rs # EnvConfig - per-environment dev_env.yaml schema
│ ├── global.rs # GlobalConfig - global config.yaml schema
│ └── template.rs # TemplateConfig - template file configuration
├── docker/
│ ├── mod.rs # Module exports, execute_command helpers
│ ├── engine.rs # Engine enum (Docker/Podman), detection, extra args
│ ├── build.rs # BuildCommand, arch_to_platform(), image_name()
│ ├── run.rs # RunCommand, MountSpec, container_name()
│ └── execute_command.rs # Command execution utilities
├── template/
│ ├── mod.rs # Module exports (ProcessContext, process_templates)
│ ├── processor.rs # Template processing logic, variable substitution
│ ├── security.rs # Path validation, traversal prevention
│ └── random.rs # Cryptographically secure random generation
└── commands/ # One file per subcommand
├── mod.rs
├── run.rs # Build + run container
├── build.rs # Build image only
├── connect.rs # Attach to running container
├── list.rs # List environments
├── status.rs # Show disk/container status
└── clean.rs # Cleanup images/containers
```
**Data flow for `flyboat run <env>`:**
1. `EnvironmentManager::new()` discovers environments in `~/.flyboat/env/`
2. `EnvFeaturizedName::parse()` extracts name and features from `name+feature1+feature2`
3. `manager.search_result(&name)` resolves environment (returns `Rc<Environment>` or error with suggestions)
4. Features are applied via `config.apply_features()`
5. Determines engine from CLI → global config → auto-detect
6. `BuildCommand` builds the image
7. `RunCommand` runs the container with mounts, ports, network
## Security Defaults
- Default network: `none` (most restrictive) in dev_env.yaml
- Ports bind to `127.0.0.1` only
- Prevents running from `/`, `/root`, `/home`, or `$HOME` directories
- Podman uses `--userns keep-id:uid=3400,gid=3400`
- Template path traversal (`..`) is rejected
- Paths are canonicalized and verified to stay within env folder
- `bytes` charset in templates requires `encoding: base64`
## Global Configuration
Location: `~/.flyboat/config.yaml`
```yaml
container_engine: docker|podman # Default: docker (auto-detects if not set, prefers podman)
```
**Resolution order for container engine:**
1. CLI flag `--engine`
2. Global config `container_engine`
3. Auto-detect (prefers Podman for rootless)
**Resolution order for architecture:**
1. CLI flag `--arch` or `-a`
2. Global config `arch`
3. System default
## dev_env.yaml Schema
```yaml
name: string # Required
disable: bool # Default: false - excludes env from list/run/build
description: string
version: string # Default: "1.0.0"
author: string # Optional
source: string # Optional URL
aliases: [string] # Alternative names
settings:
entrypoint: string # Default: bash
working_dir: string # Optional (default mount path: /project)
artifacts_folder: bool # Default: false
mounts:
- host_path: string
container_path: string
read_only: bool # Default: true
custom_args: [string] # Extra docker run args
features: # Named config overlays (see Features section)
feature_name:
settings: ... # Partial settings to override
mounts: [...] # Additional mounts to append
custom_args: [...] # Additional args to append
templates: [...] # Additional templates to append
templates: # Template file processing
- source: string # Template file path (relative to env folder)
overwrite: if_not_exists|on_build|on_run # Default: if_not_exists
variables:
var_name:
type: fixed
value: string
encoding: base64 # Optional
var_name:
type: random
charset: alphanumeric|lowercase|uppercase|mixedcase|numbers|hex|bytes
length: number # 0-65535
encoding: base64 # Required for bytes charset
```
## Template Processing
Templates allow generating config files with `{{var_name}}` placeholders replaced by fixed or random values.
**Output naming:** `config.example.toml` → `config.toml` (removes `.example` segment)
**Overwrite modes:**
- `if_not_exists` - Only generate if output doesn't exist (default)
- `on_build` - Generate during `flyboat build`
- `on_run` - Generate during `flyboat run` (fresh random values each run)
**Security:**
- Path traversal (`..`) is rejected
- Paths are canonicalized and verified to stay within env folder
- `bytes` charset requires `encoding: base64`
**Undefined variables:** Warning printed, `{{var_name}}` left as literal in output
## Features System
Features allow defining config variants in a single `dev_env.yaml` file. Activate features via CLI using `+` syntax.
**Example dev_env.yaml:**
```yaml
name: rust
settings:
network: none
features:
gpu:
custom_args: ["--gpus=all"]
network:
settings:
network: bridge
dev:
settings:
disk_access: mount
custom_args: ["-e", "DEBUG=1"]
```
**CLI usage:**
```bash
flyboat run rust+gpu # Apply gpu feature
flyboat run rust+gpu+network # Apply both in order (gpu first, then network)
flyboat build rust+dev # Build with dev feature
```
**Merge semantics:**
- `settings.*` fields: **Replace** (later features override earlier)
- `mounts`, `custom_args`, `templates`: **Append** (accumulate)
**Verbose output:**
```
Taking base configuration 'rust'
- Applying feature 'gpu'
- Applying feature 'network'
```
**Key files:**
- `EnvFeaturizedName::parse()` in `lib.rs` - parses `name+feature1+feature2` syntax
- `EnvConfig::apply_feature()` in `config/dev_env.rs` - applies single feature
- `FeatureConfig`, `PartialEnvSettings` structs in `config/dev_env.rs`
## Code Style
- Rust edition 2024
- `#![forbid(unsafe_code)]` - no unsafe code allowed
- `clippy::all = "deny"` - all clippy warnings are errors
- Use `thiserror` for error definitions
- Use `Rc<Environment>` for shared environment references
- Use `IndexMap` for ordered maps (features preserve insertion order)