Kurtosis starlark-dev
Develop and debug Kurtosis Starlark packages. Create packages from scratch, understand the plan-based execution model, use print() debugging, handle future references, and test packages locally. Use when writing or troubleshooting .star files.
install
source · Clone the upstream repo
git clone https://github.com/kurtosis-tech/kurtosis
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/kurtosis-tech/kurtosis "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/starlark-dev" ~/.claude/skills/kurtosis-tech-kurtosis-starlark-dev && rm -rf "$T"
manifest:
skills/starlark-dev/SKILL.mdsource content
Starlark Dev
Create, debug, and test Kurtosis Starlark packages.
Package structure
A minimal Kurtosis package needs two files:
my-package/ kurtosis.yml # Package metadata main.star # Entry point
kurtosis.yml
name: github.com/your-org/my-package
main.star
def run(plan, args): plan.add_service( name="my-service", config=ServiceConfig( image="nginx:latest", ports={ "http": PortSpec(number=80, transport_protocol="TCP"), }, ), )
Running packages
# Run a local package kurtosis run ./my-package # Run with parameters kurtosis run ./my-package '{"param1": "value1"}' # Run a remote package from GitHub kurtosis run github.com/ethpandaops/ethereum-package # Run with a custom config file kurtosis run github.com/ethpandaops/ethereum-package --args-file config.yaml # Dry run (plan only, no execution) kurtosis run ./my-package --dry-run
Execution model
Kurtosis Starlark executes in two phases:
- Planning phase — Your code runs and builds a plan of actions.
,add_service()
, etc. don't execute immediately — they return future references.exec() - Execution phase — The plan is executed in order. Future references are resolved to actual values.
This means you cannot use the return value of
plan.exec() in Python-level logic like if/else during the planning phase. Use plan.verify() or plan.assert() instead.
# WRONG: result is a future reference, not a real value during planning result = plan.exec(service_name="my-service", recipe=ExecRecipe(command=["echo", "hello"])) if result["output"] == "hello": # This won't work as expected plan.print("matched") # RIGHT: use plan.verify for conditional checks result = plan.exec(service_name="my-service", recipe=ExecRecipe(command=["echo", "hello"])) plan.verify(result["exit_code"], "==", 0)
Debugging with print
def run(plan, args): plan.print("Args received: {}".format(args)) service = plan.add_service( name="my-service", config=ServiceConfig(image="nginx:latest"), ) plan.print("Service IP: {}".format(service.ip_address)) plan.print("Service hostname: {}".format(service.hostname))
Common patterns
Wait for service readiness
plan.wait( service_name="my-service", recipe=GetHttpRequestRecipe(port_id="http", endpoint="/health"), field="code", assertion="==", target_value=200, timeout="60s", )
Execute commands in a service
result = plan.exec( service_name="my-service", recipe=ExecRecipe(command=["cat", "/etc/hostname"]), ) plan.verify(result["exit_code"], "==", 0) plan.print("Hostname: {}".format(result["output"]))
Upload files
config_template = read_file("./templates/config.toml") artifact = plan.render_templates( name="my-config", config={ "config.toml": struct( template=config_template, data={"key": "value"}, ), }, ) plan.add_service( name="my-service", config=ServiceConfig( image="my-image:latest", files={"/etc/myapp": artifact}, ), )
Import from other packages
dependency = import_module("github.com/org/other-package/lib.star") def run(plan, args): dependency.some_function(plan)
Testing
Use a dry-run → execute → verify workflow:
# 1. Validate the plan without executing kurtosis run --dry-run ./my-package # 2. Run and check output kurtosis run ./my-package # 3. Inspect the created enclave kurtosis enclave inspect <enclave-name> # 4. Check service logs kurtosis service logs <enclave-name> <service-name> # 5. Shell into a service to verify state kurtosis service shell <enclave-name> <service-name> # 6. Clean up after testing kurtosis clean -a
Common errors
| Error | Cause | Fix |
|---|---|---|
| Using plan result in Python logic | Use or |
| Service name typo or not yet created | Check name matches |
| Port ID mismatch | Ensure in recipes matches dict key |
| Image doesn't exist or no auth | Verify image tag, check manually |
| Running from wrong directory | Run from package root containing |