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, but only when that provider has exactly one recipe: provider: aws, azure, cloudflare, npm and homebrew work on their own. provider also drives image naming and the --provider filter. With a custom recipe whose name isn’t in the table above, set provider explicitly.
gcp has five recipes, so a GCP service needs type. Without it, pilum check and every pipeline command fail before running anything:
provider 'gcp' has several recipes; set type: to one of gcp-artifact-registry-image, gcp-cloud-run, gcp-cloud-run-from-image, gcp-cloud-run-job, gcp-mig-containerA type that names no recipe (a typo, or aws-ecs) is an error too (no recipe named '...'); it never falls back to the provider’s recipe.
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) |
watch | list | - | Extra paths, relative to where you run Pilum, whose changes count as this service’s for --only-changed (see Only changed services) |
env_vars | map or list | - | Runtime environment variables (see Environment Variables) |
secrets | map or list | - | 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
Pilum reads a number where it expects a string as its text, so cpu: 1 and PORT: 8080 work. YAML parses the number first, though: version: 1.20 becomes 1.2, so quote values whose exact text matters. A boolean where Pilum expects a string is ignored and the default is used. In env_vars and secrets, numbers and booleans become their text (ENABLED: true sets "true"). Numeric fields such as cloud_run.min_instances take plain numbers.
Environment Variables
env_vars is a map of names to values. Strings, numbers and booleans are all accepted and sent as text:
env_vars: LOG_LEVEL: info API_TIMEOUT: 30 FEATURE_FLAGS: "new-ui,dark-mode"The list form used by Kubernetes and Cloud Run YAML works too:
env_vars: - name: LOG_LEVEL value: info - name: API_TIMEOUT value: 30A value that is neither (a nested map or list), or a list entry without name, is a config error: pilum check and every pipeline command report it before running anything.
Variables are set on the deployed Cloud Run service, Cloud Run Job, MIG container or Azure Container App (az containerapp up --env-vars). AWS Lambda does not receive them; 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 # addedBuild-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/3secrets accepts the same list form as env_vars (- name: API_KEY / value: api-key:latest).
For 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. AWS Lambda ignores them. Azure Container Apps rejects them: a deploy with secrets fails with secrets are not supported for Azure Container Apps yet, so manage them with az containerapp secret set.
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, startup_probe and port. startup_probe is sent as --startup-probe. port is parsed but not sent, because Cloud Run allows a port only on the ingress container. 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, VPC connector, 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, env_vars — 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
.gitignore(at any level) and.pilumignoreare skipped. See Ignore Files. - A
pilum.yamlwithoutnameaborts discovery with<path>: missing required field 'name'. - 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