Adding a Provider
This guide walks through adding cloud provider support to Pilum. You can add a provider with just YAML, or create Go handlers for complex logic.
Three Approaches
| Approach | When to Use | Example |
|---|---|---|
| Explicit commands | Simple, static commands | command: ["aws", "s3", "sync"] |
| Generic handlers | Reusable across providers | build, docker, push |
| Provider handlers | Provider-specific logic | deploy for gcp |
Approach 1: Explicit Commands Only
The simplest approach — no Go code required.
Create the Recipe
File: recepies/my-provider-recepie.yaml
name: my-providerdescription: Deploy to My Platformprovider: my-providerservice: container
required_fields: - name: cluster description: Cluster name type: string - name: namespace description: Target namespace type: string default: default
optional_fields: - name: replicas description: Number of replicas type: int default: "1"
steps: - name: build command: go build -o ./dist/app . execution_mode: service_dir timeout: 300
- name: deploy command: kubectl apply -f k8s/ --namespace ${namespace} execution_mode: service_dir timeout: 120 tags: - deployCreate a Service
File: services/my-app/pilum.yaml
name: my-appprovider: my-providercluster: productionnamespace: apps
build: language: go version: "1.23"Test
pilum check # Validatepilum dry-run # Previewpilum deploy # DeployThat’s it! No Go code needed.
Approach 2: With Handlers
For complex providers, create handlers that generate commands dynamically.
1. Create the Recipe
name: aws-fargatedescription: Deploy to AWS ECS Fargateprovider: aws-fargateservice: container
required_fields: - name: region description: AWS region type: string - name: cluster description: ECS cluster name type: string - name: ecr_repository description: ECR repository name type: string
steps: - name: build binary execution_mode: service_dir timeout: 300
- name: build docker image execution_mode: service_dir timeout: 300
- name: ecr login execution_mode: root timeout: 60
- name: push to ecr execution_mode: root timeout: 180
- name: deploy to fargate execution_mode: root timeout: 300 tags: - deploy2. Create the Ingredient
File: ingredients/aws/fargate.go
package aws
import ( "fmt" service "github.com/sid-technologies/pilum/lib/service_info" "github.com/sid-technologies/pilum/lib/configutil")
func GenerateECRLoginCommand(svc service.ServiceInfo) []string { return []string{ "aws", "ecr", "get-login-password", "--region", svc.Region, "|", "docker", "login", "--username", "AWS", "--password-stdin", fmt.Sprintf("%s.dkr.ecr.%s.amazonaws.com", svc.Project, svc.Region), }}
func GenerateFargateDeployCommand(svc service.ServiceInfo, imageName string) []string { cluster := configutil.GetNestedString(svc.Config, "cluster", "default") serviceName := configutil.GetNestedString(svc.Config, "service_name", svc.Name)
return []string{ "aws", "ecs", "update-service", "--cluster", cluster, "--service", serviceName, "--force-new-deployment", "--region", svc.Region, }}3. Register Handlers
File: lib/registry/commands.go
import "github.com/sid-technologies/pilum/ingredients/aws"
func registerAWSHandlers(reg *CommandRegistry) { reg.Register("ecr", "aws-fargate", func(ctx StepContext) any { return aws.GenerateECRLoginCommand(ctx.Service) })
reg.Register("deploy", "aws-fargate", func(ctx StepContext) any { return aws.GenerateFargateDeployCommand(ctx.Service, ctx.ImageName) })}
func RegisterDefaultHandlers(reg *CommandRegistry) { // ... existing handlers registerAWSHandlers(reg)}Handler Matching
Handlers are matched by pattern (substring) and optionally provider:
// Matches "deploy" steps for "gcp" provider onlyregistry.Register("deploy", "gcp", handler)
// Matches "build" steps for any providerregistry.Register("build", "", handler)
// Matches "docker" in step name (e.g., "build docker image")registry.Register("docker", "", handler)Priority: Provider-specific handlers take precedence over generic ones.
| Step Name | Pattern | Provider | Matches? |
|---|---|---|---|
build binary | build | "" | Yes |
build docker image | docker | "" | Yes |
deploy to cloud run | deploy | gcp | Yes (if service is gcp) |
deploy to cloud run | deploy | aws | No (wrong provider) |
Available Variables
In explicit commands:
| Variable | Source |
|---|---|
${name} | Service name |
${tag} | --tag flag |
${provider} | Provider name |
${region} | Service region |
${project} | Service project |
command: aws s3 sync ./dist s3://${project}-${name}-${tag}Step Configuration
| Field | Type | Description |
|---|---|---|
name | string | Step name (used for handler matching) |
command | string/list | Explicit command (overrides handler) |
execution_mode | string | root or service_dir |
timeout | int | Max seconds |
retries | int | Retry count |
env_vars | map | Environment variables |
tags | list | Labels for filtering |
Checklist
- Recipe YAML in
recepies/<provider>-recepie.yaml - Recipe has:
name,description,provider,service - Required fields with descriptions
- Optional fields with defaults
- Steps ordered correctly (build → push → deploy)
- Timeouts set appropriately
- Deploy steps tagged with
deploy - Handlers registered (if needed)
- Ingredient package created (if needed)
- Tested with
pilum dry-run
Testing
# Validate configurationpilum check
# Preview generated commandspilum dry-run --tag=test
# Run build steps onlypilum build --tag=test
# Full deploymentpilum deploy --tag=v1.0.0Next Steps
- How Recipes Work — Understand the recipe system
- GCP Cloud Run — Example provider implementation