Skip to content

Quick Start

This guide deploys a Go service to GCP Cloud Run. The flow is the same for other targets; only the type and its target-specific fields change.

You need docker and an authenticated gcloud, an Artifact Registry Docker repository, and Docker configured to push to it (gcloud auth configure-docker us-central1-docker.pkg.dev). See GCP Cloud Run for the full prerequisites.

1. Initialize a Service

Run pilum init from your project root:

Terminal window
pilum init

It asks for:

  1. A provider (gcp, aws, azure, cloudflare, homebrew, npm)
  2. A service type for that provider (for GCP: cloud-run, cloud-run-job, …)
  3. The service name, then the recipe’s required fields (Enter accepts a default)
  4. Optional fields (Enter accepts the recipe default)
  5. A build language: bun, c, cpp, dotnet, go, java, kotlin, node, php, python, ruby or rust

It writes pilum.yaml with type set, and for container recipes it also writes a starter Dockerfile at _templates/<language> and sets template to match. Existing _templates/ files are never overwritten.

Pass --provider, --name and --language (plus --service for providers that have service types, and --force to overwrite an existing file) to skip the prompts. This is useful in scripts and for AI agents:

Terminal window
pilum init --provider=gcp --service=cloud-run --name=my-api --language=go

Non-interactive mode fills in recipe defaults but skips required fields that have no default. For Cloud Run that is registry_name, so add it before running pilum check.

init cannot generate gcp-cloud-run-from-image, gcp-artifact-registry-image or gcp-mig-container services; write those by hand from their provider pages. It also lists some service types (gke, cloud-functions, ecs, fargate, functions) that have no recipe yet.

2. Review pilum.yaml

After adding registry_name, the generated file looks like this:

name: my-api
provider: gcp
type: gcp-cloud-run
template: go
project: my-gcp-project
region: us-central1
registry_name: services # Artifact Registry repo name, not a full path
build:
language: go
version: "1.23"
cmd: "go build -o ./dist"
env_vars:
CGO_ENABLED: "0"
GOOS: "linux"
GOARCH: "amd64"
flags:
ldflags:
- "-s"
- "-w"
- "-extldflags '-static'"
cloud_run:
min_instances: 0
max_instances: 10
memory: 512Mi
cpu: "1"
concurrency: 80
timeout_seconds: 300
cpu_throttling: true
port: 8080

Things to know about this file:

  • type selects the recipe. Without it, a provider: gcp service falls back to gcp-artifact-registry-image and is never deployed.
  • build.flags are appended to the end of build.cmd, so the command runs as go build -o ./dist -ldflags='-s -w …'. Don’t end cmd with a package path such as . when you use flags. See Build Configuration.
  • Quote values that gcloud reads as strings. init writes cpu: 1 unquoted; Pilum ignores non-string values for string fields, so use cpu: "1".
  • Set project to your real GCP project ID; my-gcp-project is the recipe default.

You can also write this file by hand. Every field is described in Service Configuration.

3. Edit the Dockerfile

The image is built from the project root with:

Terminal window
docker build -t <image>:<tag> -t <image>:latest --build-arg SERVICE_NAME=<service dir> -f ./_templates/<template> .

so the build context is the project root and SERVICE_NAME is the service’s directory relative to it. One template can serve many services. The generated _templates/go compiles ./cmd/server inside Docker; change it to match your layout. If your Dockerfile compiles the code itself, the host-side build.cmd is optional. Remove cmd and Pilum skips the “build binary” step for this service.

4. Validate

Terminal window
pilum check
● Checking configuration of the services
Checking service my-api (recipe: gcp-cloud-run)
✓ my-api: valid
✓ All services are valid

check validates each service against its recipe’s required fields and reports the first one missing.

5. Preview the Deployment

Terminal window
pilum dry-run --tag=v1.0.0
Deploying 1 service(s)
━━━ Step 1/5: build binary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◌ my-api build binary
→ /bin/sh -c go build -o ./dist -ldflags='-s -w -extldflags '-static''
━━━ Step 2/5: build docker image ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◌ my-api build docker image
→ docker build -t us-central1-docker.pkg.dev/my-gcp-project/services/my-api:v1.0.0 -t us-central1-docker.pkg.dev/my-gcp-project/services/my-api:latest --build-arg SERVICE_NAME=. -f ./_templates/go .
━━━ Step 3/5: publish to registry ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◌ my-api publish to registry
→ docker push us-central1-docker.pkg.dev/my-gcp-project/services/my-api:v1.0.0
━━━ Step 4/5: deploy to cloud run ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◌ my-api deploy to cloud run
→ gcloud run deploy my-api --region us-central1 --platform managed --no-allow-unauthenticated --cpu-throttling --min-instances=0 --max-instances=10 --concurrency=80 --timeout=300 --project my-gcp-project --image us-central1-docker.pkg.dev/my-gcp-project/services/my-api:v1.0.0 --port=8080 --memory 512Mi --cpu 1 --clear-env-vars --clear-secrets
━━━ Step 5/5: route traffic to latest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◌ my-api route traffic to latest
→ gcloud run services update-traffic my-api --to-latest --region us-central1 --project my-gcp-project

Notes on the commands:

  • The image is <region>-docker.pkg.dev/<project>/<registry_name>/<name>:<tag>. Only the versioned tag is pushed; :latest is a local tag.
  • Cloud Run services are private by default (--no-allow-unauthenticated). Set cloud_run.allow_unauthenticated: true for a public service.
  • --clear-env-vars and --clear-secrets appear because this service declares no env_vars or secrets. Pilum sets the service’s full env and secret list on every deploy.
  • Without --tag, the image tag is the service’s version: field if set, otherwise latest.

6. Deploy

Terminal window
pilum deploy --tag=v1.0.0

Each step prints a line per service as it finishes, then a summary:

━━━ Step 1/5: build binary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ my-api (1.2s)
━━━ Step 2/5: build docker image ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ my-api (15.3s)
...
━━━ Complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ 1/1 services completed successfully
Total time: 41.3s

Add --verbose to stream each command’s output.

7. After the Deploy

Terminal window
pilum status my-api # Deployed state: status, image, URL, last update
pilum logs my-api # Recent logs (add --follow to stream)
pilum history # Past runs from .pilum/history.jsonl
pilum rollback my-api # Move traffic back to the previous healthy revision

See Rollback, Status & Logs.

Common Commands

Terminal window
# Deploy all discovered services
pilum deploy --tag=v1.0.0
# Deploy specific services (names, comma lists or globs)
pilum deploy my-api --tag=v1.0.0
# Build only (steps tagged "build")
pilum build --tag=v1.0.0
# All steps except those tagged deploy (build + push)
pilum publish --tag=v1.0.0
# Only deploy steps (images must already exist)
pilum deploy --only-tags=deploy --tag=v1.0.0
# Only services changed since main, plus their dependents
pilum deploy --only-changed --tag=v1.0.0
# List discovered services and available recipes
pilum list
pilum recipes

Project Structure

my-project/
├── .pilum/ # State directory (add to .gitignore)
│ ├── history.jsonl # Run history
│ └── deploy.lock # Deployment lock (present only during a run)
├── .pilum.yml # Optional project config: webhooks, compose dependencies
├── .pilumignore # Optional: extra discovery ignore patterns
├── _templates/
│ └── go # Dockerfile shared by container services (template: go)
└── services/
├── api/
│ ├── pilum.yaml
│ └── main.go
└── worker/
├── pilum.yaml
└── main.go

Run Pilum from my-project/. Discovery, _templates/, .pilum.yml and the Docker build context are all relative to that directory.

Next Steps