A Naive container base image update strategy

Base image update illustration

Base image updates

Security of the Software Supply Chain is a hot topic these days. When you build your app into a container, all its dependencies are captured in an immutable container image. That's great. It means that when you deploy this container image to a container runtime like Cloud Run, you can be sure that what's running is exactly the same as what you built and potentially tested. But what if you want to keep your app up to date with the latest vulnerability patches?

When it comes to patches of the OS, most of the time, all it takes to update them is to re-build and re-deploy your container. Indeed, if you use a Dockerfile, you likely use a "base image", and you likely point at a tag of this base image (e.g. you might use the latest official Node.js 16 base image with FROM node:16). A tag is a moving target that will point at the latest "version" of this image (technically called a "digest").

So, if you are continuously developing and deploying your app, problem solved. Every time you build, it will pick up whatever is the latest version of the base image. You do not get the patch as soon as it's available, but if you deploy frequently enough (e.g. once a day, a week), it's probably good enough.

But what if you are not actively developing your app? In that case, nothing will re-build and re-deploy it. Not even your CI/CD system, since no code change is pushed to your main branch. Assuming you have set up a CI/CD pipeline that builds and deploy your app, all you need to do is to regularly trigger this pipeline.

Let's take a concrete example with a git repository continuously built with Cloud Build and pushed to Cloud Run:

Automatically updating the base image of your Cloud Run service

If you haven't already, follow the instructions to Continuously deploy your Cloud Run service from git using Cloud Build. This creates a Cloud Build Trigger that triggers anytime you push to a certain branch of your GitHub repository.

All we need to do is create a Cloud Scheduler job that will run the Cloud Build Trigger every day.

Let's start by capturing your Google Cloud project ID and Cloud Build Trigger ID in environment variables. The easiest way I found to get the ID your Cloud Build Trigger, open it in the Cloud Console and then look up the ID in the URL.

PROJECT_ID=your-project-id
TRIGGER_ID=89fce451-85cb-4671-9404-f325b0373e6e

We are then going to create a dedicated identity that is going to have the permission to run the trigger:

gcloud iam service-accounts create trigger-build-every-day --description="Can trigger Cloud Build ${TRIGGER_ID}"
gcloud projects add-iam-policy-binding ${PROJECT_ID}\
      --member="serviceAccount:trigger-build-every-day@${PROJECT_ID}.iam.gserviceaccount.com" \
      --role="roles/cloudbuild.builds.editor"

Finally, we create a Cloud Scheduler job that will send a POST request to the Cloud Build API with the proper body and credentials (replace main with the name of your branch ):

gcloud scheduler jobs create http trigger-build-every-day \
    --schedule='0 12 * * *' \
    --uri=https://cloudbuild.googleapis.com/v1/projects/${PROJECT_ID}/triggers/${TRIGGER_ID}:run \
    --message-body='{"branchName": "main"}' \
    --oauth-service-account-email=trigger-build-every-day@${PROJECT_ID}.iam.gserviceaccount.com \
    --oauth-token-scope=https://www.googleapis.com/auth/cloud-platform
That's it. Your app will now automatically be built and deploy every day, even if you do not push You can manually trigger the Cloud Scheduler job to test that a new build-and-deploy is started:
gcloud scheduler jobs run trigger-build-every-day

Conclusion

This technique is very naive, and will create unnecessary builds and revisions polluting your history. Ideally, you want to only to do when the base image has actually been updated.
And we've only taled about base images, not about language packages. For package managers that pin the exact version of dependencies (like package-lock.json in Node.js), this will not automatically update the language dependencies. A developer action is required to do so in any case.