Creating r2r Extensions
Problem: You want to add custom automation commands to r2r that can be shared across teams and run consistently in any environment.
Solution: Create a Docker-based extension that r2r invokes as a container.
How Extensions Work
r2r extensions are Docker containers that:
- Receive command arguments from the CLI
- Execute your custom logic
- Return exit codes to indicate success/failure
Quick Start
1. Create Your Extension
Build a Docker image that accepts command-line arguments:
# Dockerfile
FROM alpine:latest
COPY my-extension /usr/local/bin/my-extension
ENTRYPOINT ["/usr/local/bin/my-extension"]
Your entrypoint receives all arguments passed after the extension name:
2. Register the Extension
Add your extension to .r2r/r2r-cli.yml:
extensions:
- name: 'my-ext'
image: 'ghcr.io/my-org/my-extension:latest'
description: 'My custom automation extension'
3. Use Your Extension
# Via run command
r2r run my-ext do-something
# Via alias (created automatically)
r2r my-ext do-something
Extension Configuration
Basic Configuration
extensions:
- name: 'my-ext'
image: 'ghcr.io/my-org/my-extension:latest'
description: 'What this extension does'
With Environment Variables
extensions:
- name: 'my-ext'
image: 'ghcr.io/my-org/my-extension:latest'
env:
API_KEY: '${MY_API_KEY}' # From host environment
CONFIG_PATH: '/config' # Static value
With Volume Mounts
extensions:
- name: 'my-ext'
image: 'ghcr.io/my-org/my-extension:latest'
volumes:
- '${HOME}/.my-ext:/config:ro' # Read-only config
- './output:/output:rw' # Read-write output
With Resource Limits
extensions:
- name: 'my-ext'
image: 'ghcr.io/my-org/my-extension:latest'
memory_limit: '512m'
cpu_limit: '1.0'
Local Development
During development, load from local Docker:
Full Configuration Schema
extensions:
- name: string # Required: Command name (r2r <name>)
image: string # Required: Docker image reference
description: string # Optional: Shown in help
version: string # Optional: Extension version
load_local: boolean # Optional: Skip registry pull (default: false)
env: # Optional: Environment variables
KEY: 'value'
volumes: # Optional: Volume mounts
- 'host:container:mode'
memory_limit: string # Optional: Memory limit (e.g., '512m', '2g')
cpu_limit: string # Optional: CPU limit (e.g., '0.5', '2.0')
Building Extensions
Go Extension Example
// main.go
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: my-ext <command>")
os.Exit(1)
}
switch os.Args[1] {
case "hello":
fmt.Println("Hello from my extension!")
case "version":
fmt.Println("my-ext v1.0.0")
default:
fmt.Printf("Unknown command: %s\n", os.Args[1])
os.Exit(1)
}
}
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o my-ext .
FROM alpine:latest
COPY --from=builder /app/my-ext /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/my-ext"]
Python Extension Example
#!/usr/bin/env python3
# main.py
import sys
def main():
if len(sys.argv) < 2:
print("Usage: my-ext <command>")
sys.exit(1)
command = sys.argv[1]
if command == "hello":
print("Hello from my extension!")
elif command == "version":
print("my-ext v1.0.0")
else:
print(f"Unknown command: {command}")
sys.exit(1)
if __name__ == "__main__":
main()
Repository Access
Extensions automatically receive the repository mounted at /var/task:
// Access repository files
repoRoot := os.Getenv("R2R_CONTAINER_REPOROOT") // "/var/task"
configPath := filepath.Join(repoRoot, ".r2r", "eac", "repository.yml")
Key environment variables available in extensions:
| Variable | Description |
|---|---|
R2R_CONTAINER_REPOROOT |
Repository mount path (/var/task) |
R2R_HOST_REPOROOT |
Original host path |
R2R_ENV |
Environment identifier |
R2R_OPERATION_ID |
Unique operation ID for tracing |
Publishing Extensions
To GitHub Container Registry
# Build and tag
docker build -t ghcr.io/my-org/my-extension:1.0.0 .
docker tag ghcr.io/my-org/my-extension:1.0.0 ghcr.io/my-org/my-extension:latest
# Push
docker push ghcr.io/my-org/my-extension:1.0.0
docker push ghcr.io/my-org/my-extension:latest
Multi-Platform Builds
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/my-org/my-extension:1.0.0 \
--push .
Best Practices
- Single responsibility: Each extension should do one thing well
- Exit codes: Return 0 for success, non-zero for errors
- Stdout/stderr: Use stdout for output, stderr for errors/logs
- Help command: Implement
--helporhelpsubcommand - Version command: Implement
versionsubcommand - Minimal images: Use Alpine or distroless base images
- Pin versions: Use specific tags, not just
latest
Troubleshooting
| Problem | Solution |
|---|---|
| Image not found | Check image name and registry access |
| Permission denied | Ensure entrypoint is executable |
| No output | Check container logs with docker logs |
| Wrong arguments | Extension receives args after extension name |
| Mount issues | Verify volume paths exist on host |
Tutorials | How-to Guides | Explanation | Reference
You are here: How-to Guides — task-oriented recipes that guide you through solving specific problems.