Codegolf: Build a container in Cloud Build
Tags: googlecloud, codegolf, cloudbuild
Published: 2024-03-27 01:19:28

<small>_Banner Photo by [Viktor Kiryanov](https://unsplash.com/@vki?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on Unsplash_</small>

I read Adam Ross's [Modernizing cloudbuild.yaml for Container Builds](https://dev.to/googlecloud/modernizing-cloudbuildyaml-for-container-builds-1je0) and saw all his mentions of "character counts" and it got me thinking: 

How codegolf can we get with this? 

---

> Context: like regular golf, codegolf asks you to implement something in the least amount of character possible. 

Starting with Adam's finishing iteration: 

```yaml
steps:
- id: 'Build Container Image'
  name: 'gcr.io/cloud-builders/docker:latest'
  script: docker build . --tag "${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest"

images:
- "${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest"

options:
  automapSubstitutions: true

substitutions:
  _IMAGE: service
  _LOCATION: us
  _REPO: container
```

**Initial character count: 358**

> It appears his character count doesn't inline new lines, which is effectively the same as the `wc -c` minus the `wc -l`. I'll be using the following for my character counts: 
> `expr $(wc -c cloudbuild.yaml | cut -d' ' -f1) - $(wc -l cloudbuild.yaml | cut -d ' ' -f1)`

He notes that the introduction of [Artifact Registry](https://cloud.google.com/artifact-registry/docs/) make this a bit longer than it would be in Container Registry, but we'll sit with that since it's the supported registry. 

We'll also keep with this last iteration's requirements: a project might have more than one registry, so we'll keep some parameterization. 

Each iteration I'll make sure is a valid build. For testing, I'll ensure `gcloud builds submit` runs clean in a folder containing just this `cloudbuild.yaml` file and my go-to [codegolf Dockerfile](https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/app-dev/python-frameworks-cloudrun/easteregg-shell/Dockerfile) (that's also a valid Cloud Run service, just for fun).

## What's in an ID? 

To start, Cloud Build steps don't require an ID, so we can remove that line entirely. Without an ID, the Cloud Build build details in the Google Cloud console will use the image name as the step details, so there is a slight usability disadvantage to this change, but nothing that affects functionality. 

```diff
-- id: 'Build Container Image'
-  name: 'gcr.io/cloud-builders/docker:latest'
+- name: 'gcr.io/cloud-builders/docker:latest'
```

**Character count: 329.**


## Deduplicate with dynamics

We've also got the same image ID used multiple times. We want to keep the dynamic nature of this, but we also want to use substitutions in the value itself. We can use `dynamicSubstitutions` to allow use of substitutions within other substitutions, but adding this adds ~27 characters to our file (and we're removing 45x2 characters so this is a win!)

```yaml
steps:
- name: 'gcr.io/cloud-builders/docker:latest'
  script: docker build . --tag "$_IMAGE_URI"

images:
- "$_IMAGE_URI"

options:
  automapSubstitutions: true
  dynamicSubstitutions: true

substitutions:
  _IMAGE_URI: ${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest
  _IMAGE: service
  _LOCATION: us
  _REPO: container
```

**Character count: 326.**

At this point we can also remove the superfluous quotation marks, saving **6** characters, bringing us down to **320.**

## Shortest values

Now we get to the part where we work out: What's the minimum value for the variables we're using? 

The `$PROJECT_ID` is a [default substitution](https://cloud.google.com/build/docs/configuring-builds/substitute-variable-values), so we should keep that as is, and it will be dynamic for our project. 

We're already using the shortest Artifact Registry region, the [US multi-region](https://cloud.google.com/artifact-registry/docs/repositories/repo-locations#location-mr) (the longest Google Cloud region could be up to 24 characters, so 2 is great!)

But we can possibly do better with the Artifact Registry name and the Container Name. 

`d` (for "Docker") is a valid Artifact Registry repository name, and `i` (for "image") are valid, so we can save ~15 characters there: 

```diff
...
-  _IMAGE: service
+  _IMAGE: i
   _LOCATION: us
-  _REPO: container
+  _REPO: c
```

**Character count: 306.**


## Minimal variables

Any of our underscore substitutions we can reduce down to two letters, the underscore and some useful character. (We're replacing "Image **U**RL", "**I**mage", "**R**epo", and "**L**ocation", respectively.)

```yaml
steps:
- name: gcr.io/cloud-builders/docker:latest
  script: docker build . --tag $_U

images:
- $_U

options:
  automapSubstitutions: true
  dynamicSubstitutions: true

substitutions:
  _U: ${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}:latest
  _I: i
  _L: us
  _R: d
```

**Character count: 254.**

(We're now down below what Adam was before migrating to Artifact Registry! 😅)

-----


## Remove tags

Another reduction we can make is removing the `:latest` tags. "Latest" is used in Docker where there is [a lack of tag](https://cloud.ibm.com/docs/Registry?topic=Registry-troubleshoot-docker-latest), so this is implicit in our call (and it just so happens that the Cloud Builder we are referencing has a "latest" tag. (You can check this by going to [gcr.io/cloud-builders/docker](gcr.io/cloud-builders/docker) in the browser, which will show you information about this image in its registry. This image in particular will direct you to its Artifact Registry information, because [this registry has been automatically redirected](https://github.com/GoogleCloudPlatform/cloud-builders?tab=readme-ov-file#container-registry-deprecation).)

```yaml
steps:
- name: gcr.io/cloud-builders/docker
  script: docker build . --tag $_U

images:
- $_U

options:
  automapSubstitutions: true
  dynamicSubstitutions: true

substitutions:
  _U: ${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}
  _I: i
  _L: us
  _R: d
```

**Character count: 240.**


## Putting the args back

The last step we can do to save space is to actually re-introduce the `args` parameter, rather than using `script`. In this case, we will be adding some characters for the separation of the arguments, but the biggest win is being able to remove the ~27 characters that the `automapSubstitutions` config setting adds. 

```yaml
steps:
- name: gcr.io/cloud-builders/docker
  args: ['build','.','--tag','$_U']

images:
- $_U

options:
  dynamicSubstitutions: true

substitutions:
  _U: ${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}
  _I: i
  _L: us
  _R: d
```

**Character count: 213.**

_Tip: Make sure you check what your default entrypoint is for images! In this case, I have also removed `docker`, as my args are appended to the default entrypoint. The script needs the full command (I may have spent too long debugging this issue_ 😅_)_


## Twist

Actually you only need **22 characters**: 

```
gcloud run deploy a
[ENTER]
[ENTER]
```

First time you run this you'll need 1 extra character (<kbd>ENTER</kbd>) to enable APIs, and also 1 extra character (<kbd>ENTER</kbd>) to create the Cloud Run Source Deploy image registry. (You can also use this method to automatically create the Artifact Registry for you, read more about this on my blog post: "[Auto-provisioning Artifact Registry though Cloud Run Source Deploys (glasnt.com)](https://glasnt.com/blog/auto-provisioning-artifact-registry/)").


## Summary

You can make a very short Cloud Build configuration to build images, but you should consider the readability of your scripts as you go. Addressing any cruft and modernizing your scripts can be an interesting exercise for you and your infrastructure. 

_With thanks to Adam Ross for the review!_