Skip to content

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

ApproachWhen to UseExample
Explicit commandsSimple, static commandscommand: ["aws", "s3", "sync"]
Generic handlersReusable across providersbuild, docker, push
Provider handlersProvider-specific logicdeploy 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-provider
description: Deploy to My Platform
provider: my-provider
service: 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:
- deploy

Create a Service

File: services/my-app/pilum.yaml

name: my-app
provider: my-provider
cluster: production
namespace: apps
build:
language: go
version: "1.23"

Test

Terminal window
pilum check # Validate
pilum dry-run # Preview
pilum deploy # Deploy

That’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-fargate
description: Deploy to AWS ECS Fargate
provider: aws-fargate
service: 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:
- deploy

2. 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 only
registry.Register("deploy", "gcp", handler)
// Matches "build" steps for any provider
registry.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 NamePatternProviderMatches?
build binarybuild""Yes
build docker imagedocker""Yes
deploy to cloud rundeploygcpYes (if service is gcp)
deploy to cloud rundeployawsNo (wrong provider)

Available Variables

In explicit commands:

VariableSource
${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

FieldTypeDescription
namestringStep name (used for handler matching)
commandstring/listExplicit command (overrides handler)
execution_modestringroot or service_dir
timeoutintMax seconds
retriesintRetry count
env_varsmapEnvironment variables
tagslistLabels 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

Terminal window
# Validate configuration
pilum check
# Preview generated commands
pilum dry-run --tag=test
# Run build steps only
pilum build --tag=test
# Full deployment
pilum deploy --tag=v1.0.0

Next Steps