Until recently, every time I published a post on this blog, I opened the Cloud Build console and watched for the deploy to finish.
I'd been doing this for almost 4 years (!), and it finally got old. So I got around to sending notifications to Pushover, which I introduced in [2015-03-08-1].
Along the way, I moved the Docker image build from Cloud Build to GitHub Actions. One thing led to another, and I ended up cleaning up Artifact Registry, bringing resources under Terraform, and tidying up IAM.
In the end, I didn't just get notifications. The setup became simpler and faster, and Artifact Registry usage went down too.
Why Deploy Notifications Now?
As I wrote in [2022-10-01-1], this blog moved from Heroku to Cloud Run in September 2022.
Back on Heroku, setting up a Pushover notification for completed deploys was easy. After moving to Cloud Run, I lost those notifications and just left things as they were, checking the console as described above.
A long time ago, I tried cloud-build-notifiers, which is mentioned in the Cloud Build documentation. It sends HTTP notifications using Pub/Sub and Cloud Run, but there were so many steps that I gave up halfway 😅
I Assumed It Had to Be Done on the Google Cloud Side
Since Cloud Build was doing the build and deploy, I assumed the notifications had to be implemented on the Google Cloud side as well.
As it turned out, all I needed to do was move the deploy trigger to GitHub Actions. For Pushover notifications, I already had my own reusable workflow from [2026-02-23-1].
First, I had GitHub Actions run gcloud builds submit synchronously and send a Pushover notification once it finished. Cloud Build still handled the build and deploy; GitHub Actions just waited for it to complete. With that, I finally had the notifications I'd wanted for years.
While I was at it, I also made the deploy wait for Test to succeed. Until then, the GitHub Actions Test and the Cloud Build trigger ran at the same time, so the blog got deployed even when Test failed.
Here's what the Deploy workflow looked like at that point:
jobs: test: uses: ./.github/workflows/test.yml deploy: needs: test # Wait for Test to succeed steps: - name: Deploy to Cloud Run via Cloud Build run: gcloud builds submit . --config=cloudbuild.yaml # Wait for Cloud Build to finish pushover_success: if: success() needs: [test, deploy] uses: masutaka/actions/.github/workflows/pushover.yml@main with: status: success pushover_failure: if: failure() needs: [test, deploy] uses: masutaka/actions/.github/workflows/pushover.yml@main That's when I realized GitHub Actions was only waiting for Cloud Build to finish. If GitHub Actions did the build as well, there would be one less layer in between.
Why I Moved the Build to GitHub Actions Too
At first, I just thought it was worth a try if it removed a layer. When I tried it, deploys got faster, so I kept it. I'll cover how much faster in the next section.
On AWS, it's common to see setups that build and deploy with GitHub Actions instead of CodeBuild. This setup is pretty much the same idea.
GitHub Actions now runs docker build, pushes the image to Artifact Registry, and deploys with gcloud run services update. It authenticates to Google Cloud with Workload Identity Federation, so there are no SA (Service Account) keys, which are long-lived credentials.
Here's the Deploy workflow after the move:
jobs: deploy: steps: - name: Build run: docker build --build-arg HUGO_ENV_ARG=production -t "$IMAGE" . - name: Configure Google Cloud Credentials id: google-credentials uses: google-github-actions/auth@... # v3.0.0 with: workload_identity_provider: ${{ vars.GOOGLE_WORKLOAD_IDENTITY_PROVIDER }} service_account: ${{ vars.GOOGLE_SERVICE_ACCOUNT_DEPLOY }} - name: Push run: | gcloud auth configure-docker asia-northeast1-docker.pkg.dev --quiet docker push "$IMAGE" - name: Deploy to Cloud Run run: | gcloud run services update masutaka-net \ --region=asia-northeast1 \ --image="$IMAGE" \ --quiet When a build fails, the logs show up right in GitHub Actions. I no longer need to go check the Cloud Build console.
How Much Faster Did It Get?
Here's a comparison of the time from build to the change going live on Cloud Run:
| Period | Method | Time |
|---|---|---|
| Until 9/21 | Cloud Build trigger | 1m 45s – 2m 6s |
| 9/21 – 9/22 | gcloud builds submit from GitHub Actions | 2m 20s – 2m 58s |
| From 9/22 | Build and deploy with GitHub Actions | 1m 1s – 1m 24s |
It got slower for a while because GitHub Actions had to bundle the source into a tarball and upload it to Cloud Storage. Once the build moved to GitHub Actions as well, both the upload and the Cloud Build VM startup went away, bringing it down from about 2 minutes to just over 1 minute.
Things That Got Cleaned Up Along the Way
For almost 4 years after moving to Cloud Run, I'd left the settings I created in the console untouched. That worked fine because Google Cloud, including Cloud Run, was stable. I can barely remember any incidents.
But precisely because I could leave it alone, I never noticed the bloated Artifact Registry, the excessive permissions, or the resources not managed as code. This time, I cleaned all of that up too.
Artifact Registry Usage
1,327 Docker images had piled up without ever being deleted, taking up 45.5 GB.
I set a cleanup policy to keep the 10 most recent images and delete anything older than 90 days. Usage dropped to 3 GB, a 93% reduction.
Almost Everything Under Terraform
I imported the Cloud Run service, domain mapping, uptime checks, and project IAM that I'd created in the console back in 2022 into Terraform.
The Cloud Run image and labels are set to ignore_changes, leaving deploys to GitHub Actions. Terraform manages only the service configuration, not which image is running.
I also added terraform apply to the Deploy workflow, so the deploy runs after apply finishes. This way, Terraform and workflow changes can go in a single PR.
Simpler and Safer IAM
| Before | After | |
|---|---|---|
| Cloud Run runtime SA | Default compute SA (roles/editor) | Dedicated SA with no roles |
| Legacy Cloud Build SA | 3 roles, including roles/run.admin | No longer needed, so I deleted the SA itself |
| My own account | roles/owner plus 2 other roles | roles/owner only |
This blog is just nginx serving static files, so the runtime SA doesn't need any roles.
Conclusion
All I wanted was deploy notifications, but I got these four things at once:
- Notifications (something I'd been missing since moving to Cloud Run)
- Simplicity (no more Cloud Build, almost everything in Terraform, and cleaner IAM)
- Speed (from build to live: about 2 minutes → just over 1 minute)
- Less Artifact Registry usage (45.5 GB → 3 GB)
My Google Cloud bill also dropped dramatically, but I'll save that for the next post.
I'm not sure how many people want deploy notifications like this for Cloud Run, but I hope this helps someone.


