{"owner":"k3d-io","repo":"k3d","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nk3d creates containerized k3s (lightweight Kubernetes) clusters using Docker. It runs multi-node k3s clusters on a single machine. The Go module is `github.com/k3d-io/k3d/v5`.\n\n## Build & Development Commands\n\n```bash\nmake build              # Build for local platform -> bin/k3d\nmake build-debug        # Build with debug symbols\nmake build-cross        # Cross-compile for all platforms (needs gox)\nmake install-tools      # Install required tools (golangci-lint, gox)\n```\n\nDependencies are vendored (`-mod=vendor`). The go.work workspace includes `.`, `./docgen`, and `./tools`.\n\n### Local Helper Image Overrides\n\nWhen developing helper images locally, use these env vars to point k3d at your local builds instead of the published ones (see `pkg/types/env.go`):\n\n```bash\nK3D_IMAGE_LOADBALANCER=ghcr.io/k3d-io/k3d-proxy:dev   # Override the load balancer (proxy) image\nK3D_IMAGE_TOOLS=ghcr.io/k3d-io/k3d-tools:dev           # Override the tools helper image\n```\n\n## Testing\n\n```bash\nmake test                                           # Run all unit tests\ngo test ./pkg/config/... -run TestProcessConfig     # Run a specific test\nmake e2e                                            # Run E2E tests (requires Docker, uses DIND)\nmake e2e -e E2E_INCLUDE=\"test_basic\"                # Run specific E2E test\nmake e2e -e E2E_LOG_LEVEL=trace -e E2E_FAIL_FAST=true  # E2E with debug output\n```\n\nE2E tests are bash scripts in `tests/` that run inside Docker-in-Docker containers. Unit tests use `testify` for assertions and `go-test/deep` for deep equality. Config test assets are in `pkg/config/test_assets/`.\n\n### E2E Test Framework\n\n**Execution chain:** `make e2e` -> `tests/dind.sh` (builds DIND image, starts privileged container) -> `tests/runner.sh` (discovers and batches tests) -> `test_*.sh` scripts.\n\n**Writing a new E2E test:**\n\n1. Create `tests/test_<name>.sh` (the `test_` prefix is required for auto-discovery)\n2. Make it executable (`chmod +x`)\n3. Follow the standard boilerplate:\n   ```bash\n   #!/bin/bash\n   CURR_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n   [ -d \"$CURR_DIR\" ] || { echo \"FATAL: no current dir (maybe running in zsh?)\"; exit 1; }\n   source \"$CURR_DIR/common.sh\"\n\n   ### Step Setup ###\n   LOG_FILE=\"$TEST_OUTPUT_DIR/$( basename \"${BASH_SOURCE[0]}\" ).log\"\n   exec >${LOG_FILE} 2>&1\n   export LOG_FILE\n   KUBECONFIG=\"$KUBECONFIG_ROOT/$( basename \"${BASH_SOURCE[0]}\" ).yaml\"\n   export KUBECONFIG\n   ### Step Setup ###\n\n   export CURRENT_STAGE=\"Test | <name>\"\n   # ... test logic using $EXE, info, failed, check_clusters, etc.\n   exit 0\n   ```\n4. Use unique cluster/resource names to avoid collisions with parallel tests\n5. Always clean up resources (cluster delete, registry delete) at the end\n\n**Key helpers from `tests/common.sh`:** `info`, `failed`, `passed`, `highlight`, `check_clusters`, `check_multi_node`, `check_registry`, `wait_for_pod_running_by_name`, `wait_for_pod_running_by_label`, `exec_in_node`, `docker_assert_container_label`, `k3s_assert_node_label`.\n\n**E2E environment variables:** `E2E_INCLUDE` (run only named tests), `E2E_EXCLUDE` (skip named tests), `E2E_PARALLEL` (default: 4), `E2E_EXTRA` (run `extra_test_*` files), `E2E_FAIL_FAST`, `E2E_LOG_LEVEL`, `E2E_K3S_VERSION`, `E2E_DIND_VERSION`, `E2E_KEEP` (keep runner container).\n\n## Linting & Formatting\n\n```bash\nmake fmt        # Fix formatting (gofmt)\nmake check-fmt  # Check formatting\nmake lint       # Run golangci-lint (v2.4.0)\nmake ci-lint    # Same with 5-minute timeout (CI)\nmake check      # check-fmt + lint\n```\n\n## Architecture\n\nThree-layer design:\n\n1. **CLI layer** (`cmd/`) - Cobra commands for cluster, node, registry, kubeconfig, image, config, debug subcommands. Entry point: `cmd/root.go` -> `NewCmdK3d()`.\n\n2. **Client layer** (`pkg/client/`) - Business logic and orchestration. Key files: `cluster.go` (ClusterCreate/Start/Delete/Run), `node.go` (NodeCreate/Start/Delete), `registry.go`, `loadbalancer.go`, `kubeconfig.go`.\n\n3. **Runtime layer** (`pkg/runtimes/`) - Container runtime abstraction via the `Runtime` interface in `runtime.go`. Only Docker is implemented (`pkg/runtimes/docker/`). The Docker runtime translates k3d types to Docker API calls (`translate.go`).\n\n### Key Packages\n\n- **`pkg/types/`** - Core domain types: `Cluster`, `Node`, `Registry`, `Role` (ServerRole, AgentRole, LoadBalancerRole, RegistryRole). Extensive label system (`k3d.cluster`, `k3d.role`, etc.) used to track Docker container metadata.\n- **`pkg/config/`** - Versioned config system (current: `v1alpha5`, API version `k3d.io/v1alpha5`). Includes migration (`migrate.go`), JSON schema validation (`schema.json` embedded via `//go:embed`), and config processing/transformation pipeline.\n- **`pkg/actions/`** - Node lifecycle hooks (e.g., `WriteFileAction`).\n- **`pkg/logger/`** - Logging via logrus.\n\n### Helper Components\n\n- **`proxy/`** - nginx-based load balancer with confd for dynamic upstream config. Built as `ghcr.io/k3d-io/k3d-proxy`.\n- **`tools/`** - Separate Go module (`tools/`) for the k3d-tools helper binary. Built as `ghcr.io/k3d-io/k3d-tools`.\n\n## Config System\n\nConfig files are YAML with `apiVersion: k3d.io/v1alpha5` and `kind: Simple`. The processing pipeline: read YAML -> validate JSON schema -> migrate from older versions if needed -> transform to internal types -> merge with defaults. Config versions: v1alpha2 (legacy) -> v1alpha3 -> v1alpha4 -> v1alpha5 (current).\n\n## Version & LDFLAGS\n\nVersion info is injected at build time via LDFLAGS into `version/version.go`:\n- `version.Version` - git tag\n- `version.K3sVersion` - latest stable k3s version (fetched from k3s update channel)\n- `version.HelperVersionOverride` - optional override for helper image versions\n\n## License & Contributing\n\nThis project is **MIT licensed** (Copyright 2019-2023 Thorsten Klein). All new Go source files must include the MIT copyright header present in existing files. See `CONTRIBUTING.md` for guidelines — check existing issues/PRs before opening new ones to avoid duplicates. The project follows the Contributor Covenant Code of Conduct (`CODE_OF_CONDUCT.md`).\n\n## Conventions\n\n- All Go source files have the MIT license copyright header\n- Import alias: `k3d \"github.com/k3d-io/k3d/v5/pkg/types\"` is used throughout as the canonical import for the types package\n- Docker is the only runtime, but all container operations go through the `Runtime` interface\n- Node types are identified by `Role` and tracked via Docker container labels\n\n## Issue & PR Handling\n\n**Before starting work on any issue:**\n\n1. **Check for duplicates** — search open and closed issues for the same topic (`gh issue list -S \"keyword\"` / `gh issue list -S \"keyword\" --state closed`)\n2. **Check for existing PRs** — look for open PRs that already address the issue (`gh pr list -S \"keyword\"`)\n3. **Check if it's a k3d issue** — many reports are actually k3s, Docker, or CNI issues. If so, redirect the reporter to the appropriate upstream repo\n4. **Read the full thread** — issues often evolve through discussion; the original request may have been refined or scoped down\n\n**When replying to issues:**\n\n- For questions: answer concisely, point to env vars/flags/docs, close if resolved\n- For bugs: confirm reproducibility scope, check if already fixed on `main`\n- For features: check if there's a workaround, note if a PR would be welcome\n\n**When working on PRs:**\n\n1. **Check the linked issue** — understand the full context and any decisions made in discussion\n2. **Check the PR's CI status** — don't start reviewing or building on a PR that's failing CI for unrelated reasons\n3. **Check for related/conflicting PRs** — look for other open PRs touching the same files (`gh pr list` + review changed files)\n4. **Verify the branch is up to date** with `main` before investing effort\n\n**Scope awareness:**\n\n- k3d only controls the container orchestration layer around k3s. Issues about k3s behavior, CNI plugins, Kubernetes internals, or Docker engine bugs are upstream concerns\n- The proxy (`k3d-proxy`) and tools (`k3d-tools`) images are part of this project — issues about those are in scope\n- Registry issues may involve the upstream `registry` image vs k3d's registry wiring — distinguish which layer is at fault\n\n## Deep Dive References\n\nFor detailed analysis beyond this summary, see `.planning/codebase/`:\n\n| Document | Contents |\n|----------|----------|\n| `STACK.md` | Full dependency inventory, Go version, build toolchain, Docker SDK usage |\n| `ARCHITECTURE.md` | Layer diagrams, data flow through CLI->Client->Runtime, abstraction boundaries |\n| `STRUCTURE.md` | Complete directory layout, file naming conventions, package organization |\n| `CONVENTIONS.md` | Error handling patterns, logging conventions, code style, naming rules |\n| `TESTING.md` | Unit test patterns, E2E test framework details, test helpers, fixture locations |\n| `INTEGRATIONS.md` | Docker API interaction, k3s channel server, registry (wharfie), kubeconfig handling |\n| `CONCERNS.md` | Technical debt, single-runtime limitation, config migration complexity, known fragile areas |\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nk3d creates containerized k3s (lightweight Kubernetes) clusters using Docker. It runs multi-node k3s clusters on a single machine. The Go module is `github.com/k3d-io/k3d/v5`.\n\n## Build & Development Commands\n\n```bash\nmake build              # Build for local platform -> bin/k3d\nmake build-debug        # Build with debug symbols\nmake build-cross        # Cross-compile for all platforms (needs gox)\nmake install-tools      # Install required tools (golangci-lint, gox)\n```\n\nDependencies are vendored (`-mod=vendor`). The go.work workspace includes `.`, `./docgen`, and `./tools`.\n\n### Local Helper Image Overrides\n\nWhen developing helper images locally, use these env vars to point k3d at your local builds instead of the published ones (see `pkg/types/env.go`):\n\n```bash\nK3D_IMAGE_LOADBALANCER=ghcr.io/k3d-io/k3d-proxy:dev   # Override the load balancer (proxy) image\nK3D_IMAGE_TOOLS=ghcr.io/k3d-io/k3d-tools:dev           # Override the tools helper image\n```\n\n## Testing\n\n```bash\nmake test                                           # Run all unit tests\ngo test ./pkg/config/... -run TestProcessConfig     # Run a specific test\nmake e2e                                            # Run E2E tests (requires Docker, uses DIND)\nmake e2e -e E2E_INCLUDE=\"test_basic\"                # Run specific E2E test\nmake e2e -e E2E_LOG_LEVEL=trace -e E2E_FAIL_FAST=true  # E2E with debug output\n```\n\nE2E tests are bash scripts in `tests/` that run inside Docker-in-Docker containers. Unit tests use `testify` for assertions and `go-test/deep` for deep equality. Config test assets are in `pkg/config/test_assets/`.\n\n### E2E Test Framework\n\n**Execution chain:** `make e2e` -> `tests/dind.sh` (builds DIND image, starts privileged container) -> `tests/runner.sh` (discovers and batches tests) -> `test_*.sh` scripts.\n\n**Writing a new E2E test:**\n\n1. Create `tests/test_<name>.sh` (the `test_` prefix is required for auto-discovery)\n2. Make it executable (`chmod +x`)\n3. Follow the standard boilerplate:\n   ```bash\n   #!/bin/bash\n   CURR_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n   [ -d \"$CURR_DIR\" ] || { echo \"FATAL: no current dir (maybe running in zsh?)\"; exit 1; }\n   source \"$CURR_DIR/common.sh\"\n\n   ### Step Setup ###\n   LOG_FILE=\"$TEST_OUTPUT_DIR/$( basename \"${BASH_SOURCE[0]}\" ).log\"\n   exec >${LOG_FILE} 2>&1\n   export LOG_FILE\n   KUBECONFIG=\"$KUBECONFIG_ROOT/$( basename \"${BASH_SOURCE[0]}\" ).yaml\"\n   export KUBECONFIG\n   ### Step Setup ###\n\n   export CURRENT_STAGE=\"Test | <name>\"\n   # ... test logic using $EXE, info, failed, check_clusters, etc.\n   exit 0\n   ```\n4. Use unique cluster/resource names to avoid collisions with parallel tests\n5. Always clean up resources (cluster delete, registry delete) at the end\n\n**Key helpers from `tests/common.sh`:** `info`, `failed`, `passed`, `highlight`, `check_clusters`, `check_multi_node`, `check_registry`, `wait_for_pod_running_by_name`, `wait_for_pod_running_by_label`, `exec_in_node`, `docker_assert_container_label`, `k3s_assert_node_label`.\n\n**E2E environment variables:** `E2E_INCLUDE` (run only named tests), `E2E_EXCLUDE` (skip named tests), `E2E_PARALLEL` (default: 4), `E2E_EXTRA` (run `extra_test_*` files), `E2E_FAIL_FAST`, `E2E_LOG_LEVEL`, `E2E_K3S_VERSION`, `E2E_DIND_VERSION`, `E2E_KEEP` (keep runner container).\n\n## Linting & Formatting\n\n```bash\nmake fmt        # Fix formatting (gofmt)\nmake check-fmt  # Check formatting\nmake lint       # Run golangci-lint (v2.4.0)\nmake ci-lint    # Same with 5-minute timeout (CI)\nmake check      # check-fmt + lint\n```\n\n## Architecture\n\nThree-layer design:\n\n1. **CLI layer** (`cmd/`) - Cobra commands for cluster, node, registry, kubeconfig, image, config, debug subcommands. Entry point: `cmd/root.go` -> `NewCmdK3d()`.\n\n2. **Client layer** (`pkg/client/`) - Business logic and orchestration. Key files: `cluster.go` (ClusterCreate/Start/Delete/Run), `node.go` (NodeCreate/Start/Delete), `registry.go`, `loadbalancer.go`, `kubeconfig.go`.\n\n3. **Runtime layer** (`pkg/runtimes/`) - Container runtime abstraction via the `Runtime` interface in `runtime.go`. Only Docker is implemented (`pkg/runtimes/docker/`). The Docker runtime translates k3d types to Docker API calls (`translate.go`).\n\n### Key Packages\n\n- **`pkg/types/`** - Core domain types: `Cluster`, `Node`, `Registry`, `Role` (ServerRole, AgentRole, LoadBalancerRole, RegistryRole). Extensive label system (`k3d.cluster`, `k3d.role`, etc.) used to track Docker container metadata.\n- **`pkg/config/`** - Versioned config system (current: `v1alpha5`, API version `k3d.io/v1alpha5`). Includes migration (`migrate.go`), JSON schema validation (`schema.json` embedded via `//go:embed`), and config processing/transformation pipeline.\n- **`pkg/actions/`** - Node lifecycle hooks (e.g., `WriteFileAction`).\n- **`pkg/logger/`** - Logging via logrus.\n\n### Helper Components\n\n- **`proxy/`** - nginx-based load balancer with confd for dynamic upstream config. Built as `ghcr.io/k3d-io/k3d-proxy`.\n- **`tools/`** - Separate Go module (`tools/`) for the k3d-tools helper binary. Built as `ghcr.io/k3d-io/k3d-tools`.\n\n## Config System\n\nConfig files are YAML with `apiVersion: k3d.io/v1alpha5` and `kind: Simple`. The processing pipeline: read YAML -> validate JSON schema -> migrate from older versions if needed -> transform to internal types -> merge with defaults. Config versions: v1alpha2 (legacy) -> v1alpha3 -> v1alpha4 -> v1alpha5 (current).\n\n## Version & LDFLAGS\n\nVersion info is injected at build time via LDFLAGS into `version/version.go`:\n- `version.Version` - git tag\n- `version.K3sVersion` - latest stable k3s version (fetched from k3s update channel)\n- `version.HelperVersionOverride` - optional override for helper image versions\n\n## License & Contributing\n\nThis project is **MIT licensed** (Copyright 2019-2023 Thorsten Klein). All new Go source files must include the MIT copyright header present in existing files. See `CONTRIBUTING.md` for guidelines — check existing issues/PRs before opening new ones to avoid duplicates. The project follows the Contributor Covenant Code of Conduct (`CODE_OF_CONDUCT.md`).\n\n## Conventions\n\n- All Go source files have the MIT license copyright header\n- Import alias: `k3d \"github.com/k3d-io/k3d/v5/pkg/types\"` is used throughout as the canonical import for the types package\n- Docker is the only runtime, but all container operations go through the `Runtime` interface\n- Node types are identified by `Role` and tracked via Docker container labels\n\n## Issue & PR Handling\n\n**Before starting work on any issue:**\n\n1. **Check for duplicates** — search open and closed issues for the same topic (`gh issue list -S \"keyword\"` / `gh issue list -S \"keyword\" --state closed`)\n2. **Check for existing PRs** — look for open PRs that already address the issue (`gh pr list -S \"keyword\"`)\n3. **Check if it's a k3d issue** — many reports are actually k3s, Docker, or CNI issues. If so, redirect the reporter to the appropriate upstream repo\n4. **Read the full thread** — issues often evolve through discussion; the original request may have been refined or scoped down\n\n**When replying to issues:**\n\n- For questions: answer concisely, point to env vars/flags/docs, close if resolved\n- For bugs: confirm reproducibility scope, check if already fixed on `main`\n- For features: check if there's a workaround, note if a PR would be welcome\n\n**When working on PRs:**\n\n1. **Check the linked issue** — understand the full context and any decisions made in discussion\n2. **Check the PR's CI status** — don't start reviewing or building on a PR that's failing CI for unrelated reasons\n3. **Check for related/conflicting PRs** — look for other open PRs touching the same files (`gh pr list` + review changed files)\n4. **Verify the branch is up to date** with `main` before investing effort\n\n**Scope awareness:**\n\n- k3d only controls the container orchestration layer around k3s. Issues about k3s behavior, CNI plugins, Kubernetes internals, or Docker engine bugs are upstream concerns\n- The proxy (`k3d-proxy`) and tools (`k3d-tools`) images are part of this project — issues about those are in scope\n- Registry issues may involve the upstream `registry` image vs k3d's registry wiring — distinguish which layer is at fault\n\n## Deep Dive References\n\nFor detailed analysis beyond this summary, see `.planning/codebase/`:\n\n| Document | Contents |\n|----------|----------|\n| `STACK.md` | Full dependency inventory, Go version, build toolchain, Docker SDK usage |\n| `ARCHITECTURE.md` | Layer diagrams, data flow through CLI->Client->Runtime, abstraction boundaries |\n| `STRUCTURE.md` | Complete directory layout, file naming conventions, package organization |\n| `CONVENTIONS.md` | Error handling patterns, logging conventions, code style, naming rules |\n| `TESTING.md` | Unit test patterns, E2E test framework details, test helpers, fixture locations |\n| `INTEGRATIONS.md` | Docker API interaction, k3s channel server, registry (wharfie), kubeconfig handling |\n| `CONCERNS.md` | Technical debt, single-runtime limitation, config migration complexity, known fragile areas |\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nk3d creates containerized k3s (lightweight Kubernetes) clusters using Docker. It runs multi-node k3s clusters on a single machine. The Go module is `github.com/k3d-io/k3d/v5`.\n\n## Build & Development Commands\n\n```bash\nmake build              # Build for local platform -> bin/k3d\nmake build-debug        # Build with debug symbols\nmake build-cross        # Cross-compile for all platforms (needs gox)\nmake install-tools      # Install required tools (golangci-lint, gox)\n```\n\nDependencies are vendored (`-mod=vendor`). The go.work workspace includes `.`, `./docgen`, and `./tools`.\n\n### Local Helper Image Overrides\n\nWhen developing helper images locally, use these env vars to point k3d at your local builds instead of the published ones (see `pkg/types/env.go`):\n\n```bash\nK3D_IMAGE_LOADBALANCER=ghcr.io/k3d-io/k3d-proxy:dev   # Override the load balancer (proxy) image\nK3D_IMAGE_TOOLS=ghcr.io/k3d-io/k3d-tools:dev           # Override the tools helper image\n```\n\n## Testing\n\n```bash\nmake test                                           # Run all unit tests\ngo test ./pkg/config/... -run TestProcessConfig     # Run a specific test\nmake e2e                                            # Run E2E tests (requires Docker, uses DIND)\nmake e2e -e E2E_INCLUDE=\"test_basic\"                # Run specific E2E test\nmake e2e -e E2E_LOG_LEVEL=trace -e E2E_FAIL_FAST=true  # E2E with debug output\n```\n\nE2E tests are bash scripts in `tests/` that run inside Docker-in-Docker containers. Unit tests use `testify` for assertions and `go-test/deep` for deep equality. Config test assets are in `pkg/config/test_assets/`.\n\n### E2E Test Framework\n\n**Execution chain:** `make e2e` -> `tests/dind.sh` (builds DIND image, starts privileged container) -> `tests/runner.sh` (discovers and batches tests) -> `test_*.sh` scripts.\n\n**Writing a new E2E test:**\n\n1. Create `tests/test_<name>.sh` (the `test_` prefix is required for auto-discovery)\n2. Make it executable (`chmod +x`)\n3. Follow the standard boilerplate:\n   ```bash\n   #!/bin/bash\n   CURR_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n   [ -d \"$CURR_DIR\" ] || { echo \"FATAL: no current dir (maybe running in zsh?)\"; exit 1; }\n   source \"$CURR_DIR/common.sh\"\n\n   ### Step Setup ###\n   LOG_FILE=\"$TEST_OUTPUT_DIR/$( basename \"${BASH_SOURCE[0]}\" ).log\"\n   exec >${LOG_FILE} 2>&1\n   export LOG_FILE\n   KUBECONFIG=\"$KUBECONFIG_ROOT/$( basename \"${BASH_SOURCE[0]}\" ).yaml\"\n   export KUBECONFIG\n   ### Step Setup ###\n\n   export CURRENT_STAGE=\"Test | <name>\"\n   # ... test logic using $EXE, info, failed, check_clusters, etc.\n   exit 0\n   ```\n4. Use unique cluster/resource names to avoid collisions with parallel tests\n5. Always clean up resources (cluster delete, registry delete) at the end\n\n**Key helpers from `tests/common.sh`:** `info`, `failed`, `passed`, `highlight`, `check_clusters`, `check_multi_node`, `check_registry`, `wait_for_pod_running_by_name`, `wait_for_pod_running_by_label`, `exec_in_node`, `docker_assert_container_label`, `k3s_assert_node_label`.\n\n**E2E environment variables:** `E2E_INCLUDE` (run only named tests), `E2E_EXCLUDE` (skip named tests), `E2E_PARALLEL` (default: 4), `E2E_EXTRA` (run `extra_test_*` files), `E2E_FAIL_FAST`, `E2E_LOG_LEVEL`, `E2E_K3S_VERSION`, `E2E_DIND_VERSION`, `E2E_KEEP` (keep runner container).\n\n## Linting & Formatting\n\n```bash\nmake fmt        # Fix formatting (gofmt)\nmake check-fmt  # Check formatting\nmake lint       # Run golangci-lint (v2.4.0)\nmake ci-lint    # Same with 5-minute timeout (CI)\nmake check      # check-fmt + lint\n```\n\n## Architecture\n\nThree-layer design:\n\n1. **CLI layer** (`cmd/`) - Cobra commands for cluster, node, registry, kubeconfig, image, config, debug subcommands. Entry point: `cmd/root.go` -> `NewCmdK3d()`.\n\n2. **Client layer** (`pkg/client/`) - Business logic and orchestration. Key files: `cluster.go` (ClusterCreate/Start/Delete/Run), `node.go` (NodeCreate/Start/Delete), `registry.go`, `loadbalancer.go`, `kubeconfig.go`.\n\n3. **Runtime layer** (`pkg/runtimes/`) - Container runtime abstraction via the `Runtime` interface in `runtime.go`. Only Docker is implemented (`pkg/runtimes/docker/`). The Docker runtime translates k3d types to Docker API calls (`translate.go`).\n\n### Key Packages\n\n- **`pkg/types/`** - Core domain types: `Cluster`, `Node`, `Registry`, `Role` (ServerRole, AgentRole, LoadBalancerRole, RegistryRole). Extensive label system (`k3d.cluster`, `k3d.role`, etc.) used to track Docker container metadata.\n- **`pkg/config/`** - Versioned config system (current: `v1alpha5`, API version `k3d.io/v1alpha5`). Includes migration (`migrate.go`), JSON schema validation (`schema.json` embedded via `//go:embed`), and config processing/transformation pipeline.\n- **`pkg/actions/`** - Node lifecycle hooks (e.g., `WriteFileAction`).\n- **`pkg/logger/`** - Logging via logrus.\n\n### Helper Components\n\n- **`proxy/`** - nginx-based load balancer with confd for dynamic upstream config. Built as `ghcr.io/k3d-io/k3d-proxy`.\n- **`tools/`** - Separate Go module (`tools/`) for the k3d-tools helper binary. Built as `ghcr.io/k3d-io/k3d-tools`.\n\n## Config System\n\nConfig files are YAML with `apiVersion: k3d.io/v1alpha5` and `kind: Simple`. The processing pipeline: read YAML -> validate JSON schema -> migrate from older versions if needed -> transform to internal types -> merge with defaults. Config versions: v1alpha2 (legacy) -> v1alpha3 -> v1alpha4 -> v1alpha5 (current).\n\n## Version & LDFLAGS\n\nVersion info is injected at build time via LDFLAGS into `version/version.go`:\n- `version.Version` - git tag\n- `version.K3sVersion` - latest stable k3s version (fetched from k3s update channel)\n- `version.HelperVersionOverride` - optional override for helper image versions\n\n## License & Contributing\n\nThis project is **MIT licensed** (Copyright 2019-2023 Thorsten Klein). All new Go source files must include the MIT copyright header present in existing files. See `CONTRIBUTING.md` for guidelines — check existing issues/PRs before opening new ones to avoid duplicates. The project follows the Contributor Covenant Code of Conduct (`CODE_OF_CONDUCT.md`).\n\n## Conventions\n\n- All Go source files have the MIT license copyright header\n- Import alias: `k3d \"github.com/k3d-io/k3d/v5/pkg/types\"` is used throughout as the canonical import for the types package\n- Docker is the only runtime, but all container operations go through the `Runtime` interface\n- Node types are identified by `Role` and tracked via Docker container labels\n\n## Issue & PR Handling\n\n**Before starting work on any issue:**\n\n1. **Check for duplicates** — search open and closed issues for the same topic (`gh issue list -S \"keyword\"` / `gh issue list -S \"keyword\" --state closed`)\n2. **Check for existing PRs** — look for open PRs that already address the issue (`gh pr list -S \"keyword\"`)\n3. **Check if it's a k3d issue** — many reports are actually k3s, Docker, or CNI issues. If so, redirect the reporter to the appropriate upstream repo\n4. **Read the full thread** — issues often evolve through discussion; the original request may have been refined or scoped down\n\n**When replying to issues:**\n\n- For questions: answer concisely, point to env vars/flags/docs, close if resolved\n- For bugs: confirm reproducibility scope, check if already fixed on `main`\n- For features: check if there's a workaround, note if a PR would be welcome\n\n**When working on PRs:**\n\n1. **Check the linked issue** — understand the full context and any decisions made in discussion\n2. **Check the PR's CI status** — don't start reviewing or building on a PR that's failing CI for unrelated reasons\n3. **Check for related/conflicting PRs** — look for other open PRs touching the same files (`gh pr list` + review changed files)\n4. **Verify the branch is up to date** with `main` before investing effort\n\n**Scope awareness:**\n\n- k3d only controls the container orchestration layer around k3s. Issues about k3s behavior, CNI plugins, Kubernetes internals, or Docker engine bugs are upstream concerns\n- The proxy (`k3d-proxy`) and tools (`k3d-tools`) images are part of this project — issues about those are in scope\n- Registry issues may involve the upstream `registry` image vs k3d's registry wiring — distinguish which layer is at fault\n\n## Deep Dive References\n\nFor detailed analysis beyond this summary, see `.planning/codebase/`:\n\n| Document | Contents |\n|----------|----------|\n| `STACK.md` | Full dependency inventory, Go version, build toolchain, Docker SDK usage |\n| `ARCHITECTURE.md` | Layer diagrams, data flow through CLI->Client->Runtime, abstraction boundaries |\n| `STRUCTURE.md` | Complete directory layout, file naming conventions, package organization |\n| `CONVENTIONS.md` | Error handling patterns, logging conventions, code style, naming rules |\n| `TESTING.md` | Unit test patterns, E2E test framework details, test helpers, fixture locations |\n| `INTEGRATIONS.md` | Docker API interaction, k3s channel server, registry (wharfie), kubeconfig handling |\n| `CONCERNS.md` | Technical debt, single-runtime limitation, config migration complexity, known fragile areas |\n","category":"root","tokens":2304}]}