Skip to content

Introduction

Pilum is a cloud-agnostic build and deployment CLI. Define your service once in a pilum.yaml, then build, publish and deploy it with one command.

Terminal window
pilum deploy --tag=v1.0.0

Pilum runs the build → push → deploy pipeline. Your infrastructure-as-code (Terraform, Pulumi) still owns the resources themselves: projects, registries, networks, IAM. Pilum ships code onto them.

Why Pilum?

ChallengePilum Solution
Provider lock-inMove a service between targets by changing its type and target-specific block
Slow sequential deploysParallel execution with auto-sized worker pools
Complex CI/CD scriptsDeclarative YAML recipes; CI calls one command
Per-provider validation codeRecipe-driven validation — no Go code per provider

The Cooking Metaphor

Pilum uses a cooking metaphor:

  • Recipes (recipes/) — Deployment workflows: required fields plus ordered steps. The built-in recipes are embedded in the binary.
  • Ingredients (ingredients/) — Provider-specific command generators (gcloud, docker, az, sam, wrangler, …)
  • Services — Your applications, each discovered via a pilum.yaml file

Deployment Targets

Each service picks a recipe with its type field. Pilum ships 10 built-in recipes:

typeWhat it does
gcp-cloud-runBuild, push to Artifact Registry, deploy a Cloud Run service — docs
gcp-cloud-run-jobBuild, push, deploy (and optionally execute) a Cloud Run Job — docs
gcp-cloud-run-from-imageDeploy a Cloud Run service from a pre-built image (no build or push) — docs
gcp-artifact-registry-imageBuild and push an image to Artifact Registry, no deploy — docs
gcp-mig-containerBuild, push and roll a new container image onto an existing Compute Engine managed instance group — docs
azure-container-appsBuild, push to ACR, deploy to Azure Container Apps — docs
aws-lambdasam build and sam deploy — docs
cloudflare-pagesInstall, build and deploy a static site with wrangler — docs
npmVersion, build and publish an npm package — docs
homebrewCross-compile a Go CLI and update a Homebrew tap formula — docs

Run pilum recipes to list them, and pilum recipes <name> to see a recipe’s fields and steps.

Features

  • Recipe-driven deployments and validation — Each recipe declares its required fields and steps; pilum check validates every service against its recipe
  • Parallel execution — Services run concurrently within each step; worker counts are sized from CPU cores, memory and the Docker daemon’s resources
  • Dependency waves — depends_on orders build, deploy and execute steps; a failed service skips its dependents
  • Build cache warming — Fills the shared Go, Node, Python, Rust, JVM, .NET or PHP dependency cache once before the parallel builds (details)
  • Docker layer cache — Opt-in registry layer cache (build.docker_cache) so fresh CI runners reuse unchanged image layers (details)
  • Rollback — pilum rollback returns Cloud Run services, Cloud Run Jobs and managed instance groups to their previous version (details)
  • Status, logs and history — pilum status, pilum logs and pilum history show what is deployed and what ran
  • Environments — Per-environment overrides (staging, prod) via an environments block and --env (details)
  • Git-aware filtering — --only-changed deploys only services affected by changes since a base ref (their own files, Go packages they import, lockfiles, Dockerfile templates), plus their dependents (details)
  • Step filtering — Run only build steps, only deploy steps, or any tag combination
  • Deployment locks — Prevent concurrent deploys from the same checkout, with stale-lock cleanup
  • CI integration — GitHub commit statuses (--github-status) and HTTP webhooks on start, success and failure (webhooks)
  • Local development — pilum compose generates a docker-compose.yml for your services and their databases (details)
  • Dry-run mode — Print every command without running anything
  • JSON output — --json for scripting and AI agents
  • Editor support — JSON Schema for autocompletion and validation of pilum.yaml

How It Works

  1. Discovery — Pilum finds pilum.yaml files up to four directories below the current directory, skipping paths matched by .gitignore files and .pilumignore (ignore files)
  2. Environment merge — With --env, each service’s environments.<env> block is merged onto its base config
  3. Matching — Each service is matched to a recipe by its type field (for example type: gcp-cloud-run). Without type, Pilum uses the provider’s recipe if it has only one; GCP has several, so GCP services need type
  4. Locking — A deployment lock (.pilum/deploy.lock) prevents concurrent runs
  5. Warming — When build steps will run, shared build caches are filled once
  6. Orchestration — Steps run in recipe order. Within a step, services run in parallel; when services declare depends_on, build, deploy and execute steps run in dependency waves
  7. Recording — Results are appended to .pilum/history.jsonl

A three-service dry run where api and worker depend on database looks like this (commands trimmed):

━━━ Warming build caches ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◌ go (3 services, auto) warm build cache
→ go build ./services/database ./services/api ./services/worker
━━━ Step 1/5: build binary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Wave 1/2:
◌ database build binary
Wave 2/2:
◌ api build binary
◌ worker build binary
━━━ Step 2/5: build docker image ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
...
━━━ Step 3/5: publish to registry ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◌ database publish to registry
◌ api publish to registry
◌ worker publish to registry
━━━ Step 4/5: deploy to cloud run ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Wave 1/2:
◌ database deploy to cloud run
Wave 2/2:
◌ api deploy to cloud run
◌ worker deploy to cloud run
━━━ Step 5/5: route traffic to latest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
...

Waves apply to steps tagged build, deploy or execute; push steps always run flat in parallel. Services without dependencies share a single wave. When a service fails, its dependents in later waves are skipped, and once any step fails, the remaining steps are skipped (except steps marked always_run). Use --no-deps to disable wave ordering.

Next Steps