This guide deploys the full Cloud Optimization Engine stack:
- Scanner API on Cloud Run (
app/) - IAM, Workload Identity Federation, and Cloud Scheduler (
terraform-bootstrap/) - Optional demo resources for scan validation (
terraform/)
- Google Cloud SDK (
gcloud) installed and authenticated - Terraform
>= 1.5 - Docker (for local image build/push)
- A GCP project with billing enabled
Set environment variables:
export PROJECT_ID="your-gcp-project-id"
export REGION="us-central1"
export SERVICE_NAME="cloud-cost-engine"
export IMAGE_NAME="dockerhub_user/cloud-optimization-engine"Point gcloud to the target project:
gcloud config set project "$PROJECT_ID"Enable required services:
gcloud services enable run.googleapis.com compute.googleapis.com secretmanager.googleapis.com cloudscheduler.googleapis.com iam.googleapis.com cloudresourcemanager.googleapis.comCreate the Secret Manager secret used by Cloud Run:
printf '%s' 'https://hooks.slack.com/services/XXX/YYY/ZZZ' | gcloud secrets create slack-webhook-url --data-file=-If it already exists, add a new version:
printf '%s' 'https://hooks.slack.com/services/XXX/YYY/ZZZ' | gcloud secrets versions add slack-webhook-url --data-file=-Build and push the app image:
docker build -t "$IMAGE_NAME:latest" ./app
docker push "$IMAGE_NAME:latest"Deploy service:
gcloud run deploy "$SERVICE_NAME" \
--project="$PROJECT_ID" \
--region="$REGION" \
--platform=managed \
--image="$IMAGE_NAME:latest" \
--set-env-vars="GOOGLE_CLOUD_PROJECT=$PROJECT_ID" \
--set-secrets="SLACK_WEBHOOK_URL=slack-webhook-url:latest"Create terraform-bootstrap/terraform.tfvars:
project_id = "your-gcp-project-id"
region = "us-central1"
cloud_run_service_name = "cloud-cost-engine"
github_repository = "OWNER/REPO"Apply bootstrap infrastructure:
cd terraform-bootstrap
terraform init
terraform apply
cd ..This creates:
cloud-cost-engine-sacloud-scheduler-invoker-sagithub-deploy-sa- GitHub Workload Identity Provider
- Scheduler job calling
GET /scanon Cloud Run with OIDC
Re-deploy Cloud Run to run with the dedicated runtime service account:
gcloud run deploy "$SERVICE_NAME" \
--project="$PROJECT_ID" \
--region="$REGION" \
--platform=managed \
--image="$IMAGE_NAME:latest" \
--service-account="cloud-cost-engine-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--set-env-vars="GOOGLE_CLOUD_PROJECT=$PROJECT_ID" \
--set-secrets="SLACK_WEBHOOK_URL=slack-webhook-url:latest"Use this only if you want predictable scanner findings for testing.
Create terraform/terraform.tfvars:
project_id = "your-gcp-project-id"Apply:
cd terraform
terraform init
terraform apply
cd ..Set repository secrets used by .github/workflows/deploy.yml:
DOCKERHUB_USERNAMEDOCKERHUB_TOKENGCP_PROJECT_IDGCP_WORKLOAD_IDENTITY_PROVIDER(fromterraform-bootstrapoutput)GCP_DEPLOY_SERVICE_ACCOUNT(fromterraform-bootstrapoutput)
Then push to main (or run workflow manually) to build image and deploy Cloud Run.
Health endpoint:
curl "$(gcloud run services describe $SERVICE_NAME --region $REGION --format='value(status.url)')/"Trigger scan manually:
curl "$(gcloud run services describe $SERVICE_NAME --region $REGION --format='value(status.url)')/scan"Check logs:
gcloud logging read 'resource.type="cloud_run_revision" AND resource.labels.service_name="cloud-cost-engine"' --limit=50Use docs/troubleshooting.md for known issues and fixes:
- Secret injection errors
- Scheduler
401 Unauthorized - GitHub OIDC auth problems
- Empty scan results