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:
pilum initIt asks for:
- A provider (
gcp,aws,azure,cloudflare,homebrew,npm) - A service type for that provider (for GCP:
cloud-run,cloud-run-job, …) - The service name, then the recipe’s required fields (Enter accepts a default)
- Optional fields (Enter accepts the recipe default)
- A build language:
bun,c,cpp,dotnet,go,java,kotlin,node,php,python,rubyorrust
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:
pilum init --provider=gcp --service=cloud-run --name=my-api --language=goNon-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-apiprovider: gcptype: gcp-cloud-runtemplate: goproject: my-gcp-projectregion: us-central1registry_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: 8080Things to know about this file:
typeselects the recipe. Without it, aprovider: gcpservice falls back togcp-artifact-registry-imageand is never deployed.build.flagsare appended to the end ofbuild.cmd, so the command runs asgo build -o ./dist -ldflags='-s -w …'. Don’t endcmdwith a package path such as.when you useflags. See Build Configuration.- Quote values that gcloud reads as strings.
initwritescpu: 1unquoted; Pilum ignores non-string values for string fields, so usecpu: "1". - Set
projectto your real GCP project ID;my-gcp-projectis 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:
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
pilum check● Checking configuration of the services Checking service my-api (recipe: gcp-cloud-run)✓ my-api: valid✓ All services are validcheck validates each service against its recipe’s required fields and reports the first one missing.
5. Preview the Deployment
pilum dry-run --tag=v1.0.0Deploying 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-projectNotes on the commands:
- The image is
<region>-docker.pkg.dev/<project>/<registry_name>/<name>:<tag>. Only the versioned tag is pushed;:latestis a local tag. - Cloud Run services are private by default (
--no-allow-unauthenticated). Setcloud_run.allow_unauthenticated: truefor a public service. --clear-env-varsand--clear-secretsappear because this service declares noenv_varsorsecrets. Pilum sets the service’s full env and secret list on every deploy.- Without
--tag, the image tag is the service’sversion:field if set, otherwiselatest.
6. Deploy
pilum deploy --tag=v1.0.0Each 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.3sAdd --verbose to stream each command’s output.
7. After the Deploy
pilum status my-api # Deployed state: status, image, URL, last updatepilum logs my-api # Recent logs (add --follow to stream)pilum history # Past runs from .pilum/history.jsonlpilum rollback my-api # Move traffic back to the previous healthy revisionCommon Commands
# Deploy all discovered servicespilum 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 dependentspilum deploy --only-changed --tag=v1.0.0
# List discovered services and available recipespilum listpilum recipesProject 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.goRun Pilum from my-project/. Discovery, _templates/, .pilum.yml and the Docker build context are all relative to that directory.
Next Steps
- Service Configuration — Full
pilum.yamlreference - Build Configuration — Build commands, flags and cache warming
- Monorepo Guide — Many services, dependencies and waves
- CLI Reference — All commands and flags