CLI Commands
This page is the reference for the pilum CLI. Every flag and default below comes from the command definitions in cmd/. Run pilum <command> --help to see the same information in your terminal.
Command Overview
| Command | Aliases | What it does |
|---|---|---|
deploy | up | Run every recipe step: build, push, deploy |
build | b, make | Run steps tagged build |
publish | p | Run every step not tagged deploy |
push | ps | Run steps tagged push |
dry-run | dr | Print the commands a pipeline would run |
rollback | — | Roll services back to their previous version (GCP) |
status | st | Show the state of deployed services |
logs | log | Show or stream logs for one deployed service |
history | hist | Show recorded pipeline runs and rollbacks |
list | ls | List discovered services |
check | validate | Validate pilum.yaml files against their recipes |
recipes | providers | List recipes or describe one |
init | — | Create a pilum.yaml |
compose | docker-compose, dc | Generate a docker-compose.yml for local development |
delete-builds | clean | Delete each service’s dist/ directory |
completion | — | Generate a shell completion script |
telemetry | — | Show or change the telemetry setting |
pilum --version prints pilum <version>, for example pilum v0.9.0.
Global Flags
These flags work on every command.
| Flag | Short | Default | Description |
|---|---|---|---|
--verbose | -v | false | Stream command output in real time |
--quiet | -q | false | Minimal output (CI-friendly) |
--json | false | Output as JSON for scripting | |
--no-gitignore | false | Don’t read .gitignore files (the root one or nested ones) for ignore patterns during service discovery |
--verbose, --quiet and --json are mutually exclusive.
Pipeline Flags
deploy, build, publish, push and dry-run share these flags. dry-run has every flag except --dry-run.
| Flag | Short | Default | Description |
|---|---|---|---|
--tag | -t | "" | Image or release tag. See Tag resolution |
--dry-run | -D | false | Print commands without running them (not on dry-run, which always does this) |
--debug | -d | false | Print each command, working directory, timeout and environment (secret-looking values redacted) |
--timeout | -T | 60 | Step timeout in seconds. A recipe step’s timeout replaces the default; passing -T replaces both. See Timeouts and retries |
--retries | -r | 3 | Extra attempts after a failed command. See Timeouts and retries |
--max-workers | 0 | Parallel workers per step. 0 sizes the pool automatically | |
--only-tags | "" | Run only steps that have at least one of these tags (comma-separated) | |
--exclude-tags | "" | Skip steps that have any of these tags (comma-separated) | |
--only-changed | false | Run only services affected by changes since --since, plus services that depend on them. See Only changed services | |
--since | "" | Git ref for --only-changed. Defaults to the default branch, preferring origin/main (or origin/master) over a local branch | |
--no-deps | false | Ignore depends_on and run every step flat-parallel | |
--no-warm | false | Skip the build-cache warming phase that runs before builds | |
--env | -e | "" | Merge the named block from each service’s environments: onto its config |
--provider | "" | Only run services whose provider matches (case-insensitive), e.g. gcp | |
--force | -f | false | Take the deployment lock even if another run holds it |
--github-status | false | Post a commit status to GitHub. See GitHub Commit Status | |
--recipe-path | "" | Load recipes from this directory instead of the built-in recipes |
Timeouts and retries
Timeout. Each step’s timeout is, in order:
--timeout(-T), when you pass it. It also counts when it comes fromPILUM_TIMEOUTor atimeout:key in.pilum.yml.- The recipe step’s
timeout. - The default, 60 seconds.
Every built-in recipe step sets a timeout (300s for builds, 120–180s for pushes and deploys, 600s for execute job, 900s for the MIG wait for stable), so without -T those apply. pilum build -T 1800 gives every step, and the warm phase, 1800 seconds.
-T replaces the recipe’s value in both directions: -T 30 makes a 300-second build step time out after 30 seconds. Before 0.10 a recipe’s timeout always won and -T only reached steps without one. A step that runs out of time fails with command timed out after 300s.
Retries. A step’s own retries always wins. Otherwise --retries, when you pass it, beats the step’s default_retries; without it, default_retries applies, then the --retries default of 3. Most built-in deploy steps set default_retries: 2. Before 0.9 default_retries was ignored.
Tag resolution
--tag defaults to an empty string, not latest. What happens with no tag depends on the recipe:
- Every recipe gets the same resolved tag:
--tag, then the service’sversion:frompilum.yaml, thenlatest. This covers image names, every step handler, and${tag}/${build.version}in custom steps. - Cloudflare Pages deploys
latest(or a tag equal toproduction_branch) to the production branch; any other tag becomes a preview branch. - npm skips
set versionforlatest, so the version inpackage.jsonis published. - Homebrew needs a real version: with neither
--tagnorversion:, the run fails before anything starts witha homebrew release needs a version; pass --tag or set version: in pilum.yaml. gcp-mig-containerrejectslatestbefore anything runs: every release needs a unique tag.
Pass --tag explicitly for any deploy you care about.
Worker sizing
With --max-workers 0, Pilum sizes each step separately:
- Steps tagged
build, and untagged steps, are CPU-bound. Workers = CPU cores ÷ cores per build (build.resources.cpu, default 2), further capped by memory: 75% of RAM ÷ the largest per-language build memory among the services. Steps that calldockerare sized against the Docker daemon’s CPUs and memory (on Docker Desktop, the VM), not the host. Each build also gets aGOMAXPROCSshare of the cores. - Steps tagged
push,deployorexecutewait on the network and getmin(tasks, 16)workers.
--max-workers N sets the worker count for every step. Pilum prints the plan it chose at the start of each step.
Pipeline Commands
pilum deploy
pilum deploy [services...] [flags]Runs every recipe step for each selected service, in recipe order: typically build, push, deploy. With no service names, it runs every discovered service. Takes the deployment lock and records the run in history.
Flags: pipeline flags.
pilum deploy --tag v1.2.0pilum deploy api worker --tag v1.2.0pilum up api --tag v1.2.0 --env prodpilum build
pilum build [services...] [flags]Runs steps tagged build. If you pass --only-tags, it replaces the build default. pilum build --help also prints a build reference by language.
Flags: pipeline flags.
pilum build --tag v1.2.0pilum b apipilum publish
pilum publish [services...] [flags]Runs every step not tagged deploy. This is not exactly build then push: steps tagged execute, and untagged steps, also run. The default applies only when you pass neither --only-tags nor --exclude-tags; either flag replaces it.
Flags: pipeline flags.
pilum publish --tag v1.2.0pilum push
pilum push [services...] [flags]Runs steps tagged push, normally docker push of an image built earlier. If you pass --only-tags, it replaces the push default.
Flags: pipeline flags.
pilum push api --tag v1.2.0pilum dry-run
pilum dry-run [services...] [flags]Prints the command each step would run, wave by wave, without executing anything. It doesn’t take the deployment lock, skips the Docker preflight check and isn’t recorded in history. It accepts every pipeline flag except --dry-run, so you can preview a filtered run exactly. pilum deploy --dry-run does the same thing.
pilum dry-run --tag v1.2.0pilum dr api --only-tags deploy --env prodExample output for a Cloud Run service api and a Cloud Run job worker that depends on it (trimmed):
Deploying 2 service(s)
━━━ Warming build caches ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ◌ go (2 services, auto) warm build cache → go build ./services/api ./services/worker
━━━ Step 1/5: build binary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Wave 1/2: ◌ api build binary → /bin/sh -c go build -o ./dist/app . Wave 2/2: ◌ worker build binary → /bin/sh -c go build -o ./dist/app .
━━━ Step 2/5: build docker image ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Wave 1/2: ◌ api build docker image → docker build --platform linux/amd64 -t us-central1-docker.pkg.dev/my-project/services/api:v1.2.0 -t us-central1-docker.pkg.dev/my-project/services/api:latest --build-arg SERVICE_NAME=services/api -f ./_templates/go . ...
━━━ Step 3/5: publish to registry ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ◌ api publish to registry → docker push us-central1-docker.pkg.dev/my-project/services/api:v1.2.0 ...
━━━ Step 4/5: deploy to cloud run / deploy job ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Wave 1/2: ◌ api deploy to cloud run → gcloud run deploy api --region us-central1 --platform managed --no-allow-unauthenticated --project my-project --image us-central1-docker.pkg.dev/my-project/services/api:v1.2.0 --clear-env-vars --clear-secrets ...
━━━ Step 5/5: route traffic to latest / execute job ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Wave 1/2: ◌ api route traffic to latest → gcloud run services update-traffic api --to-latest --region us-central1 --project my-project Wave 2/2: ◌ worker execute jobA step that generates no command (here execute job, because job.execute_on_deploy is not true) is listed without an arrow line and does nothing.
With --json, the output is a single object:
{ "dry_run": true, "steps": [ { "service": "api", "step": "publish to registry", "command": "docker push us-central1-docker.pkg.dev/my-project/services/api:v1.2.0" } ]}Rollback
pilum rollback
pilum rollback <services...> [flags]Rolls one or more deployed services back to the version before the one currently running. Run it again to step back further. At least one service name is required.
| Recipe | What rollback does |
|---|---|
gcp-cloud-run, gcp-cloud-run-from-image | Moves 100% of traffic to the previous healthy revision. Nothing is rebuilt; that revision’s full config (env, CPU, secrets) comes back with it. |
gcp-cloud-run-job | Points the job at the image of its previous execution, keeping the job’s current config. |
gcp-mig-container | Rolls the group onto the previous <template_base> instance template, using the same surge settings as a deploy, then waits until the group is stable. |
Any other recipe is rejected with exit code 6: rollback is not supported for <service> (recipe <key>).
| Flag | Short | Default | Description |
|---|---|---|---|
--to | "" | Target revision (Cloud Run service), image tag or reference (job), or template name or tag (MIG). Default: the previous version. Works with exactly one service instance | |
--yes | -y | false | Skip the confirmation prompt. Required when not interactive |
--dry-run | -D | false | Show the plan and commands without changing anything |
--force | -f | false | Take the deployment lock even if another run holds it |
--timeout | -T | 60 | Timeout per command in seconds |
--debug | -d | false | Enable debug output |
--env | -e | "" | Apply an environments: override block |
Behavior:
- The previous version is read from the cloud provider, not from local history, so rollback works from any machine, including CI.
- All services are planned in parallel first. If planning fails for any service (for example, it has no earlier revision), nothing changes.
- Rollback asks
Roll back N service(s)? (y/N). Without--yesit refuses to run when stdin is not a terminal or is/dev/null, when any ofCI,GITHUB_ACTIONS,GITLAB_CI,CIRCLECI,JENKINS_URLorBUILDKITEis set, or in--jsonmode. - It takes the deployment lock and is recorded in history as a
rollbackentry. - A multi-region service rolls back every region.
--toneeds a single instance, so name it as"api (us-east1)". - The next
pilum deployof a Cloud Run service sends traffic to the new revision again, because the recipe ends withroute traffic to latest. That step also resets any traffic split made by hand.
pilum rollback api --dry-run # show the plan onlypilum rollback api # previous revision, asks firstpilum rollback api --to api-00041-xyz # a specific revisionpilum rollback nightly-job --to v1.4.2 # job: image tagpilum rollback api worker --yes # no prompt (required in CI)See Rollback, Status & History for a walkthrough.
Observability Commands
pilum status
pilum status [services...] [flags]Queries each service’s provider in parallel and prints its state, region, image (or MIG template and stable instance count) and URL. Services with provider npm or homebrew are skipped. Supported: Cloud Run services and jobs, MIGs, Azure Container Apps, AWS Lambda and Cloudflare Pages.
Ready, Running, Succeeded and Active show as healthy. Error, NotReady and Failed show as failures, and Unknown as a warning.
| Flag | Short | Default | Description |
|---|---|---|---|
--timeout | -T | 30 | Query timeout in seconds |
--debug | -d | false | Enable debug output |
--env | -e | "" | Apply an environments: override block |
--provider | "" | Only query services with this provider |
pilum statuspilum st api --env prodpilum status --jsonpilum logs
pilum logs <service> [flags]Prints recent logs for exactly one service, or streams them with --follow. If the name matches a multi-region service, the first region is used; name a specific instance as "api (us-east1)".
Supported: Cloud Run services and jobs, Azure Container Apps, AWS Lambda and Cloudflare Pages. gcp-mig-container is not supported; use Cloud Logging for the group’s instances. npm and Homebrew services return an error.
| Flag | Short | Default | Description |
|---|---|---|---|
--lines | -n | 50 | Number of lines to show (GCP only) |
--follow | -f | false | Stream logs in real time. On GCP this runs gcloud beta run services logs tail (or jobs logs tail), which needs gcloud’s log-streaming component |
--timeout | -T | 30 | Query timeout in seconds (not applied with --follow) |
--debug | -d | false | Print the generated logs command |
--env | -e | "" | Apply an environments: override block |
pilum logs apipilum logs api -n 200pilum log api --followpilum history
pilum history [flags]Shows recorded runs, newest first. Every non-dry-run deploy, build, publish and push, and every rollback, appends one line to .pilum/history.jsonl in the project root. Nothing is recorded if Pilum can’t find a project root (see Deployment Lock).
| Flag | Short | Default | Description |
|---|---|---|---|
--limit | -n | 10 | Number of entries to show (0 shows all) |
--service | -s | "" | Only entries that include this service. Exact match on the display name, so a multi-region instance is "api (us-east1)" |
Deployment History
abc12def 2026-10-07 14:32 deploy v1.2.3 ✓ 10/10 (4m12s) 98fe76ba 2026-10-07 12:10 rollback ✓ 1/1 (38s)The count is successful step results out of the total, one per service per step. The file is append-only JSONL, so appends from different machines merge cleanly if you commit it.
pilum historypilum hist -n 20 -s apipilum history --jsonConfiguration Commands
pilum list
pilum list [flags]Lists every service discovered under the project root, with provider, project, region and runtime service.
| Flag | Short | Default | Description |
|---|---|---|---|
--env | -e | "" | Apply an environments: override block |
--provider | "" | Only list services with this provider |
Found 2 services:
• api Provider: gcp Project: my-project Region: us-central1 Service:
• worker Provider: gcp Project: my-project Region: us-central1 Service:Service shows runtime.service from pilum.yaml, which is usually empty. --json returns name, provider, project, region, type and path for each service.
pilum ls --provider gcppilum list --jsonpilum check
pilum check [services...] [flags]Validates each service: name and provider must be set (provider is inferred from a built-in type), and every required field of the matching recipe must be present unless the recipe gives it a default. It stops at the first invalid service with exit code 2. With --json, it reports every service.
| Flag | Short | Default | Description |
|---|---|---|---|
--env | -e | "" | Apply an environments: override block before validating |
● Checking configuration of the services Checking service api (recipe: gcp-cloud-run)✓ api: valid Checking service worker (recipe: gcp-cloud-run-job)✓ worker: valid✓ All services are validA failure looks like this:
✗ error checking service api: recipe 'gcp-cloud-run' requires field 'registry_name': Artifact Registry repository name (bare name, e.g. "my-repo" — not a full host/path)check finds recipes the same way the pipeline does, so a service that passes check is one a deploy can run. It fails when no recipe matches:
✗ error checking service api: no recipe named 'gcp-cloudrun' - did you mean 'gcp-cloud-run'?✗ error checking service api: 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-containerIt also reports env_vars and secrets entries it can’t read (a nested map, or a list entry without name).
check always validates against the built-in recipes; it has no --recipe-path.
pilum recipes
pilum recipes [name]Without an argument, lists the built-in recipes. With a name, shows its provider and service, required fields, optional fields with defaults, and steps with their tags and timeouts. It always reads the built-in recipes.
gcp-cloud-run Deploy to Google Cloud Run
Provider: gcp / cloud-run
Required Fields
project GCP project ID (default: my-gcp-project) region GCP region to deploy to (default: us-central1) name Service name (used for Cloud Run service and container naming) registry_name Artifact Registry repository name (bare name, e.g. "my-repo" — not a full host/path) template Dockerfile template name (path relative to _templates directory)...Steps
1. build binary [build] timeout: 300s 2. build docker image [build] timeout: 300s 3. publish to registry [push] timeout: 120s 4. deploy to cloud run [deploy] timeout: 180s 5. route traffic to latest [deploy] timeout: 120spilum recipespilum recipes gcp-mig-containerpilum providers --jsonSee How Recipes Work for all ten built-in recipes.
pilum init
pilum init [flags]Creates pilum.yaml in the current directory. Interactive by default: it asks for provider, service type, name, required and optional fields, and build language. For container recipes it also writes a starter Dockerfile at _templates/<language> (unless one exists) and sets template to match. Starter Dockerfiles exist for bun, dotnet, go, java, node, python and rust; for other languages, init asks you to create _templates/<name> yourself.
It is non-interactive when --provider, --name and --language are all set. Providers with service types (gcp, aws, azure, cloudflare) then also need --service.
| Flag | Short | Default | Description |
|---|---|---|---|
--provider | -p | "" | gcp, aws, azure, cloudflare, homebrew or npm |
--service | -s | "" | Service type, e.g. cloud-run, cloud-run-job, mig-container, lambda, container-apps, pages |
--name | -n | "" | Service name |
--language | -l | "" | bun, c, cpp, dotnet, go, java, kotlin, node, php, python, ruby or rust |
--force | -f | false | Overwrite an existing pilum.yaml without asking |
pilum initpilum init --provider gcp --service cloud-run --name my-api --language gopilum init -p cloudflare -s pages -n my-site -l node --forceThe generated file sets type: <provider>-<service>. It offers only service types that have a recipe: for gcp, cloud-run, cloud-run-job, cloud-run-from-image, artifact-registry-image and mig-container. String fields are written quoted, so cpu: "1" stays a string.
pilum compose
pilum compose [services...] [flags]Writes a docker-compose.yml that runs the selected services locally, plus infrastructure dependencies (Postgres, MySQL, MariaDB, Redis, Mongo or a custom image) declared under compose.dependencies in .pilum.yml. You must pass --all or at least one service name.
| Flag | Short | Default | Description |
|---|---|---|---|
--all | false | Include every discovered service | |
--registry | -R | "" | Registry prefix for images (overrides pilum.yaml) |
--tag | -t | latest | Image tag |
--output | -o | docker-compose.yml | Output file path |
--env | -e | "" | Apply an environments: override block |
--provider | "" | Only include services with this provider | |
--no-deps | false | Leave out infrastructure dependencies | |
--env-file | "" | Also write a .env template with service URLs, e.g. apps/web/.env.example |
pilum compose --allpilum compose api worker -o compose.dev.ymlpilum dc --all --no-deps --registry ghcr.io/myorgSee Local Development and Project Config for the dependency format.
Utility Commands
pilum delete-builds
pilum delete-builds [services...]Deletes the dist/ directory inside each selected service’s directory. No flags beyond the global ones.
pilum cleanpilum delete-builds apipilum completion
pilum completion bash|zsh|fish|powershellPrints a completion script for the given shell.
# Bashsource <(pilum completion bash)
# Zshpilum completion zsh > "${fpath[1]}/_pilum"
# Fishpilum completion fish | source
# PowerShellpilum completion powershell | Out-String | Invoke-Expressionpilum telemetry
pilum telemetry status | enable | disable | lastShows or changes whether Pilum shares anonymous build telemetry. On your machine it is off unless you turn it on; the GitHub Action turns it on by default. status says which setting decided (DO_NOT_TRACK, PILUM_TELEMETRY, the saved setting or the default), disable also deletes the install ID, and last prints the exact payload last sent. See Telemetry for what is and isn’t sent.
Service Selection
Commands that take [services...] select services by name. With no names, they act on every service discovered from the current directory down (list and compose search from the project root).
| Form | Example | Matches |
|---|---|---|
| Name | pilum deploy api | api, in every region if it’s multi-region |
| Several names | pilum deploy api worker | Both services |
| Comma list | pilum deploy api,worker | Both services (pipeline commands and rollback) |
| Glob | pilum deploy 'api-*' | Every service whose name matches; ? and [...] also work |
| One region | pilum deploy "api (us-east1)" | Only the us-east1 instance of a multi-region service |
Quote globs and region names so the shell passes them through unchanged. An unknown name prints a warning with a suggestion. If none of the names you gave match anything, the command fails with exit code 3.
Filters combine with names:
pilum deploy --provider gcp --tag v1.2.0pilum deploy --only-changed --tag v1.2.0pilum deploy --only-changed --since v1.1.0 --tag v1.2.0--only-changed compares against --since (default: the default branch, preferring origin/main) and also counts uncommitted and untracked files. A service is affected by a change in its directory, in a Go package it imports, in its workspace’s lockfile or manifest, in its Dockerfile template, or in a path under watch:. Every service that depends on an affected one through depends_on, directly or transitively, is included too. When git can’t compare (shallow clone, unknown ref) it warns and selects everything; outside a git repository it does the same. When nothing is affected, it prints No services changed; nothing to deploy and exits 0, even if you named services (since 0.10). See Only changed services.
Step Tags and Filtering
Every built-in recipe step carries one or more tags:
| Tag | Meaning | Ordering | Workers |
|---|---|---|---|
build | Compiles code or builds an image | Dependency waves | CPU and memory budget |
push | Uploads an artifact to a registry | Flat parallel | Up to 16 |
deploy | Changes what runs in the cloud | Dependency waves | Up to 16 |
execute | Runs a job | Dependency waves | Up to 16 |
--only-tags keeps steps with at least one listed tag; --exclude-tags drops steps with any listed tag. Matching is case-insensitive. A step with no tags is dropped by any --only-tags filter.
# Build and push, skip deploy stepspilum deploy --exclude-tags deploy --tag v1.2.0
# Only deploy steps; the images must already existpilum deploy --only-tags deploy --tag v1.2.0
# Several tagspilum deploy --only-tags build,push --tag v1.2.0Waves. For steps tagged build, deploy or execute, services run in waves ordered by depends_on: a service waits until everything it depends on has finished that step. If a service fails, its dependents in later waves are skipped. --no-deps turns waves off. check does not detect dependency cycles; a run with a cycle fails at the first wave-ordered step with circular dependency detected involving: <service>.
Failures. Once any step fails, Pilum lets the other services finish that step, then skips every later step except those marked always_run (such as npm’s restore package manifest). The exit code reflects the original failure.
Warming. Before the first build step, Pilum fills shared build caches once (for example, a single go build over all Go services’ packages). It runs only when a build step will run, and is skipped with --no-warm, or per service with build.warm: false. A failed warm prints Warming failed; continuing with cold builds and the builds still run. See Build Cache Warming.
Docker preflight. If any step that will run calls docker, Pilum checks the daemon first (10-second limit) and fails before building anything if it isn’t running. When a service sets build.docker_cache, it also checks that the active buildx builder can export a registry cache, and if not, turns the cache off for the run with a warning. Dry runs skip both checks.
JSON Output
--json prints machine-readable output instead of text. It is supported by list, check, recipes, status, logs, history, compose, init, rollback, delete-builds and dry-run. When one of these commands other than dry-run fails, it prints {"success": false, "error": "..."} and exits with the matching exit code.
pilum list --jsonpilum recipes gcp-cloud-run --jsonpilum status --jsonpilum dry-run --json --tag v1.2.0Deployment Lock
deploy, build, publish, push and rollback take a lock file at .pilum/deploy.lock in the project root, so two runs can’t change the same project at once. A second run fails with exit code 7:
deployment locked by PID 12345 on my-host (started 2026-10-07T10:00:00Z, command: deploy, services: [api worker]). Use --force to override- The lock is cleared automatically when the holding process is dead or the lock is more than 1 hour old.
- It is released on normal exit and on
Ctrl+C(SIGINT) or SIGTERM. --forceremoves an existing lock and takes it.- Dry runs don’t take the lock.
The project root is the nearest directory, from the current one upward, that contains package.json, cdk.json, tsconfig.json, .gitignore, go.mod or Cargo.toml. Without one, no lock is taken and no history is recorded.
GitHub Commit Status
pilum deploy --tag "$GITHUB_SHA" --github-statusWith --github-status, Pilum posts pending to the commit before the run, then success or failure, using the context pilum/<command> (for example pilum/deploy). It is opt-in: the flag is required, although its help text says “auto-detected”. It also needs all of these environment variables, or it silently posts nothing:
GITHUB_ACTIONS=trueGITHUB_TOKEN, with permission to write commit statusesGITHUB_SHA, the commit to updateGITHUB_REPOSITORY, asowner/repo
GitHub Actions sets all of them except GITHUB_TOKEN, which you pass in:
- name: Deploy run: pilum deploy --tag ${{ github.sha }} --github-status env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Webhooks for start, success and failure are configured in .pilum.yml; see Project Config and Environments & CI.
Environment Overrides
pilum deploy --env prod --tag v1.2.0--env prod deep-merges environments.prod onto each service’s base config: scalars replace, maps merge, lists replace. Services without an environments: block ignore --env. A service that has the block but not the named environment is an error that lists the available ones. See Service Configuration.
Environment Variables and .pilum.yml
Pilum reads .pilum.yml from the current directory and looks up environment variables named PILUM_ plus the flag name, upper-cased, with hyphens as underscores. Each acts as that flag’s default for deploy, build, publish, push, dry-run, rollback, status and logs:
| Variable | Acts as |
|---|---|
PILUM_TAG | --tag |
PILUM_ENV | --env |
PILUM_PROVIDER | --provider |
PILUM_TIMEOUT, PILUM_RETRIES | --timeout, --retries |
PILUM_DEBUG | --debug |
PILUM_DRY_RUN | --dry-run |
PILUM_FORCE | --force (overrides the deployment lock) |
PILUM_SINCE, PILUM_ONLY_CHANGED | --since, --only-changed |
PILUM_ONLY_TAGS, PILUM_EXCLUDE_TAGS, PILUM_MAX_WORKERS | --only-tags, --exclude-tags, --max-workers |
PILUM_YES, PILUM_TO | rollback --yes, rollback --to |
PILUM_TELEMETRY=1 or 0 turns telemetry on or off for that shell, and DO_NOT_TRACK=1 always turns it off.
A flag on the command line always wins, then the variable, then a top-level key with the flag’s name in .pilum.yml, then the built-in default.
Unprefixed names (TAG, ENV, DEBUG, TIMEOUT) are not read. Before 0.9 they were, so a CI job’s own TAG or ENV silently changed what Pilum deployed. If you relied on that, rename the variable or pass the flag.
Exit Codes
| Code | Meaning |
|---|---|
0 | Success |
1 | General error |
2 | Configuration error (bad YAML, missing fields, recipe load failure) |
3 | No services found (discovery errors, named services not found) |
4 | Deployment error (command failure, timeout, step failure) |
5 | I/O error |
6 | Invalid arguments (unknown provider, recipe or language; rollback refused) |
7 | Lock conflict (another run holds the deployment lock) |
Running deploy with no service names when nothing is discovered prints a warning and exits 0. So does --only-changed when no service is affected, with or without service names.
Next Steps
- Quick Start — Deploy your first service
- How Recipes Work — Steps, tags and the built-in recipes
- Rollback, Status & History — Operating deployed services
- Troubleshooting — Common errors and fixes