Service Configuration
Each service has a pilum.yaml that says what the service is, which recipe deploys it, and how to build it. Pilum discovers these files automatically; see Discovery.
Basic Structure
name: my-servicedescription: My servicetype: gcp-cloud-run # Selects the recipeproject: my-gcp-projectregion: us-central1registry_name: servicestemplate: go # Dockerfile at _templates/go
build: language: go cmd: "go build -o ./dist/app ."
env_vars: LOG_LEVEL: info
secrets: DATABASE_URL: database-url:latestRequired Fields
Every service needs:
| Field | Description |
|---|---|
name | Service identifier, used in commands, image names and cloud resource names. Allowed characters: a-z, A-Z, 0-9, ., -, _ |
type or provider | Which recipe to use. Set type; provider is then inferred for the built-in recipes |
Each recipe adds its own required fields (for example, gcp-cloud-run requires project, region, registry_name and template). pilum recipes <type> lists them, and pilum check reports any that are missing. The provider pages document each one.
type and provider
type names the recipe. The built-in values are:
type | Inferred provider | Provider page |
|---|---|---|
gcp-cloud-run | gcp | GCP Cloud Run |
gcp-cloud-run-job | gcp | GCP Cloud Run Jobs |
gcp-cloud-run-from-image | gcp | Cloud Run from Image |
gcp-artifact-registry-image | gcp | Artifact Registry Image |
gcp-mig-container | gcp | Managed Instance Group |
azure-container-apps | azure | Azure Container Apps |
aws-lambda | aws | AWS Lambda |
cloudflare-pages | cloudflare | Cloudflare Pages |
npm | npm | npm |
homebrew | homebrew | Homebrew |
Pilum looks up the recipe by type. If type is missing it falls back to provider and picks the first recipe for that provider in alphabetical order. provider also drives image naming and the --provider filter. With a custom recipe whose name isn’t in the table above, set provider explicitly.
Top-Level Fields
| Field | Type | Default | Description |
|---|---|---|---|
name | string | - | Service name (required) |
description | string | - | Human-readable description. Used as the formula desc by Homebrew |
type | string | - | Recipe name (see above) |
provider | string | inferred from type | gcp, aws, azure, cloudflare, homebrew, npm |
project | string | - | GCP project ID; Azure resource group (required by most recipes) |
region | string | - | Deployment region |
regions | list | - | Deploy one instance per region (see Multi-Region) |
registry_name | string | - | Bare registry name: Artifact Registry repository (GCP), ACR name (Azure). Not a full host or path |
template | string | - | Dockerfile name under _templates/ in the project root (container recipes) |
image | string | - | gcp-cloud-run-from-image: the full image reference to deploy. gcp-mig-container: the repository to push to, without a tag |
image_name | string | name | Last path segment of the image name, when it should differ from the service name |
version | string | - | Image tag used when --tag is not given. Without either, the tag is latest |
license | string | - | SPDX license identifier, written into the Homebrew formula |
depends_on | list | - | Names of services this one depends on (see Dependencies) |
env_vars | map | - | Runtime environment variables (see Environment Variables) |
secrets | map | - | Runtime secret references (see Secrets) |
sidecars | list | - | Extra containers for Cloud Run services (see Sidecars) |
environments | map | - | Per-environment overrides (see Environment Overrides) |
build | map | - | How to build the service — see Build Configuration |
Target-specific blocks are listed in Target-Specific Blocks.
Image names and tags
For recipes that build an image, Pilum composes the image reference from the service fields:
| Provider | Image |
|---|---|
gcp | <region>-docker.pkg.dev/<project>/<registry_name>/<image_name>:<tag> (if registry_name is empty, the project ID is used as the repository; a registry_name containing docker.pkg.dev or gcr.io is used as-is) |
azure | <registry_name>.azurecr.io/<image_name>:<tag> |
gcp-mig-container | <image>:<tag> |
<image_name> defaults to name. The tag is --tag, else version, else latest.
Value Types
String fields must be YAML strings. Pilum silently ignores a number or boolean where it expects a string, so quote values like cpu: "1", version: "1.23" and PORT: "8080". Numeric fields such as cloud_run.min_instances take plain numbers.
Environment Variables
env_vars is a map of names to string values:
env_vars: LOG_LEVEL: info API_TIMEOUT: "30" FEATURE_FLAGS: "new-ui,dark-mode"Variables are set on the deployed Cloud Run service, Cloud Run Job or MIG container. Azure Container Apps and AWS Lambda do not receive them (for Lambda, set them in your SAM template).
Target blocks can carry their own env_vars: cloud_run.env_vars, container_app.env_vars, lambda.env_vars and mig.env_vars. Pilum merges them with the top-level map, and top-level values win on conflicts:
env_vars: LOG_LEVEL: info
cloud_run: env_vars: LOG_LEVEL: debug # overridden by the top-level value GOMEMLIMIT: 450MiB # addedThe JSON schema doesn’t list env_vars under cloud_run, container_app or lambda yet, so editors may flag these keys even though Pilum reads them.
Build-time variables go in build.env_vars instead; see Build Configuration.
Secrets
secrets maps an environment variable name to a secret reference:
secrets: DATABASE_URL: database-url:latest API_KEY: projects/my-gcp-project/secrets/api-key/versions/3For GCP targets the value is secret-name:version or a full projects/<project>/secrets/<name>/versions/<version> path; Pilum converts the full path to name:version for gcloud --set-secrets. Secrets are applied to Cloud Run services, Cloud Run Jobs and MIG containers. Azure Container Apps and AWS Lambda ignore them.
Multi-Region Deployments
name: my-apitype: gcp-cloud-runproject: my-projectregistry_name: servicestemplate: goregions: - us-central1 - europe-west1 - asia-east1Pilum expands this into three service instances named my-api (us-central1), my-api (europe-west1) and my-api (asia-east1). Each gets its own image path (the region is part of the Artifact Registry host) and its own deploy. When regions is set, region is ignored. To target one instance, quote the full name:
pilum deploy "my-api (europe-west1)" --tag=v1.0.0Dependencies
depends_on orders services. Pilum groups services into waves by dependency depth: services in the same wave run in parallel, and waves run in sequence.
name: databasetype: gcp-cloud-run# ...
# services/api/pilum.yamlname: apitype: gcp-cloud-rundepends_on: - databaseWaves apply to steps tagged build, deploy and execute; push steps run flat. If a service fails, its dependents in later waves are skipped. --no-deps disables wave ordering. --only-changed also selects the dependents of changed services. pilum check does not detect dependency cycles; a cycle fails at deploy time.
Environment Overrides
environments holds per-environment overrides that are merged onto the base config when you pass --env:
name: apitype: gcp-cloud-runproject: acme-devregion: us-central1registry_name: servicestemplate: go
environments: staging: project: acme-staging prod: project: acme-prod region: us-east1 cloud_run: min_instances: 2pilum deploy --env=prod --tag=v1.0.0Merge rules:
- Scalars are replaced, maps are merged key by key, and lists are replaced entirely.
- A service without an
environmentsblock ignores--env. - A service that has an
environmentsblock but not the requested environment is an error, which lists the available names. - Without
--env, theenvironmentsblock is dropped and the base config is used.
check, list, status, logs, rollback and compose also accept --env. See Environments & CI.
Sidecars
gcp-cloud-run and gcp-cloud-run-from-image services can run extra containers next to the main (ingress) container:
sidecars: - name: otel-collector image: us-docker.pkg.dev/my-project/tools/otel-collector:0.98.0 memory: 256Mi cpu: "0.5" env_vars: OTEL_LOG_LEVEL: info secrets: OTEL_TOKEN: otel-token:latest args: ["--config=/etc/otel/config.yaml"]Sidecar keys: name, image, memory, cpu, env_vars, secrets, args, command, depends_on, plus port and startup_probe, which Pilum currently parses but does not pass to gcloud. When sidecars are present, the main container’s port is always sent (default 8080). See GCP Cloud Run.
Target-Specific Blocks
Each recipe reads its own nested block. Field-by-field references are on the provider pages.
| Block | Used by | Summary |
|---|---|---|
cloud_run | gcp-cloud-run, gcp-cloud-run-from-image | Scaling, memory/CPU, concurrency, timeout, port, HTTP/2, Cloud SQL, public access (allow_unauthenticated), ingress, VPC connector, service account, no_traffic, revision tag, env_vars — GCP Cloud Run |
job | gcp-cloud-run-job | Task timeout, retries, parallelism, task count, CPU/memory, service account, Cloud SQL, execute_on_deploy — Cloud Run Jobs |
mig | gcp-mig-container | Instance group name and zone/region, template base name, machine type, network, rollout surge/unavailable, wait timeout, env_vars — Managed Instance Group |
container_app | azure-container-apps | Environment, replicas, CPU/memory, ingress port and visibility — Azure Container Apps |
lambda | aws-lambda | Only env_vars is read; memory, timeout and runtime belong in your SAM template — AWS Lambda |
cloudflare | cloudflare-pages | Account ID, token env var, production branch, package manager — Cloudflare Pages |
npm | npm | Scope, registry, access, token env var, package manager — npm |
homebrew | homebrew | Project URL, tap URL, token env var — Homebrew |
runtime.service is also accepted; pilum list prints it, but nothing else uses it.
Discovery
- Pilum searches for files named exactly
pilum.yamlin the directory you run it from and up to four levels below it. pilum listandpilum composesearch from the project root instead: the nearest directory, at or above the current one, that containspackage.json,cdk.json,tsconfig.json,.gitignore,go.modorCargo.toml. The deployment lock and history (.pilum/) also live in that root. Run Pilum from the project root and the two agree.- Paths matched by
.gitignoreand.pilumignoreare skipped. See Ignore Files. - A
pilum.yamlwithoutnameaborts discovery. - The service’s directory (relative to where you run Pilum) is its path:
service_dirsteps run there, and it’s passed todocker buildasSERVICE_NAME. - Project-wide settings (webhooks, compose dependencies) live in
.pilum.yml, not inpilum.yaml. See Project Configuration.
Complete Example
name: payment-apidescription: Payment processing APItype: gcp-cloud-runproject: acme-paymentsregion: us-central1registry_name: servicestemplate: go
build: language: go version: "1.23" # Package path last, so no build.flags (they would be appended after it) cmd: "go build -ldflags='-s -w' -o ./dist/payment-api ./cmd/api" env_vars: CGO_ENABLED: "0" GOOS: linux GOARCH: amd64
env_vars: LOG_LEVEL: info
secrets: STRIPE_KEY: stripe-key:latest DATABASE_URL: payment-db-url:latest
cloud_run: min_instances: 1 max_instances: 50 memory: 1Gi cpu: "2" concurrency: 100 timeout_seconds: 30
depends_on: - payment-migrations
environments: staging: project: acme-payments-staging cloud_run: min_instances: 0Next Steps
- Build Configuration — The
buildblock in detail - Project Configuration —
.pilum.ymlwebhooks and compose dependencies - GCP Cloud Run —
cloud_runand sidecar options - CLI Reference — All available commands