Setting up gcloud takes more than a minute on GitHub Runners
TL;DR
Since GitHub Runners are weak, extracting takes a long time:
On self hosted Runners this only takes a second.
Expected behavior
There should be a change, where these files get downloaded since Network is faster than local CPU.
Observed behavior
No response
Action YAML
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
install_components: 'kubectl,skaffold'
Log output
Additional information
No response
What OS/operating system? I'm not sure what you mean by "these files get downloaded since Network is faster than local CPU".
Os is ubuntu-latest - so either Ubuntu 22.04 or 24.04
I'm not sure what you mean by "these files get downloaded since Network is faster than local CPU".
Instead of issuing a tar xz either:
- try an archive without compression
- provide an extracted copy of the archive, where each file can be downloaded (in parallel).
- have an option where the extracted files are pushed to GitHub cache.
- try an archive without compression
As far as I know, only tarballs are available for download: https://cloud.google.com/sdk/docs/install
- provide an extracted copy of the archive, where each file can be downloaded (in parallel).
You're welcome to do this yourself, but we have requirements about authoritative sources for binaries at Google. We (the authors of this GitHub Action) also have no control over the gcloud CLI or its publishing process.
- have an option where the extracted files are pushed to GitHub cache
The extracted files are cached using tool-cache.
Additionally, I just tested this and it's pretty consistently about 16s to download and unarchive.
The extracted files are cached using tool-cache.
At least for us we always see a tar xz being invoked/taking >1 minute - thus I assume there is no cache being used.
Additionally, I just tested this and it's pretty consistently about 16s to download and unarchive.
Don't know what to tell you - we consistently get above 1 minute.
The only thing different seems that you do npm processing previously - which we don't.
Our parameters are:
install_components: kubectl,skaffold
version: latest
skip_install: false
I'm not sure how to help here. I pulled a couple examples from https://github.com/search?q=%22google-github-actions%2Fsetup-gcloud%40v2%22+path%3A**%2F*.yaml&type=code and looked at recent GitHub Actions runs for setup-gcloud, and they are all consistently ~16s:
- https://github.com/abcxyz/github-metrics-aggregator/actions/runs/12956176722/job/36142047969
- https://github.com/mobile-dev-inc/Maestro/actions/runs/13027962418/job/36340731772
- https://github.com/cerbos/cerbos/actions/runs/12032174443/job/33543389243
- https://github.com/stackrox/stackrox/actions/runs/13022061217/job/36324622604
- https://github.com/cybozu-go/cke/actions/runs/12801980294/job/35692335841
The npm processing is to compile the typescript into nodejs, it's not relevant.
Are you sure it's not the installation of skaffold and kubectl that is taking a long time?
I'm not sure how to help here. I pulled a couple examples from https://github.com/search?q=%22google-github-actions%2Fsetup-gcloud%40v2%22+path%3A**%2F*.yaml&type=code and looked at recent GitHub Actions runs for setup-gcloud, and they are all consistently ~16s:
- https://github.com/abcxyz/github-metrics-aggregator/actions/runs/12956176722/job/36142047969
- https://github.com/mobile-dev-inc/Maestro/actions/runs/13027962418/job/36340731772
- https://github.com/cerbos/cerbos/actions/runs/12032174443/job/33543389243
- https://github.com/stackrox/stackrox/actions/runs/13022061217/job/36324622604
- https://github.com/cybozu-go/cke/actions/runs/12801980294/job/35692335841
The npm processing is to compile the typescript into nodejs, it's not relevant.
Are you sure it's not the installation of skaffold and kubectl that is taking a long time?
That is a good point. Would be great if this action would log when installing components. Based on your search, I did https://github.com/search?q=%22google-github-actions%2Fsetup-gcloud%40v2%22+skaffold+path%3A**%2F*.yaml&type=code and it seems like next to no-one is installing e.g. skaffold using this action. As such it probably is not a very optimized scenario and we probably should install skaffold and kubectl another way.
Explicitly now installed both kubectl and skaffold via gcloud components install. Each run takes ~30s on GitHub hosted Runners.
So basically, performance wise, it seems better to use GitHub Actions specific tools (which utilize tool cache). In our case:
- https://github.com/Azure/setup-kubectl for
kubectl - https://github.com/hiberbee/github-action-skaffold for
skaffold
Would be good to add a warning to install_components, that it is slow and without any caching.
We're also experiencing 1 minute for every gcloud install. Having the option to use the github cache from within the official setup-gcloud action would be ideal!
I'm not sure I understand what you mean by "use the github cache from within the official setup-gcloud action"?
I mean that, ideally, when using the setup-gcloud action, the installation of components (kubectl, skaffold, etc) would be cached, so that every new ran doesn't take >1 minute executing the setup-gcloud action.
I might be mistaken, but that's not something we have control over? Tool caches are not shared between invocations on GitHub-managed runners.
See the official docs from Github Actions Cache:
https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#about-caching-workflow-dependencies
Alternatively, if you are caching the package managers listed below, using their respective setup-* actions requires minimal configuration and will create and restore dependency caches for you.
So if setup-node, setup-python, etc. are able to leverage the GitHub cache to speed up builds, perhaps setup-gcloud could do the same?
@javiercr
how about this one?
- name: gcloud caching
uses: actions/cache@v4
with:
path: ${{ runner.tool_cache }}/gcloud
key: ${{ runner.os }}-gcloud-v515.0.0
- uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ vars.GCP_PROJECT_ID }}
version: "515.0.0"
@k-jun that seems to work! thank you!
Wonder if it's possible to use it with version: latest rather than hard coding the version number.
@javiercr
I didn't check yet though, it's gonna be something like this.
- name: gcloud latest hash
id: gcloud_latest_hash
run: |
HASH=$(curl https://raw.githubusercontent.com/google-github-actions/setup-cloud-sdk/main/data/versions.json | md5sum)
echo "hash=$HASH" >> $GITHUB_OUTPUT
- name: gcloud caching
uses: actions/cache@v4
with:
path: ${{ runner.tool_cache }}/gcloud
key: ${{ runner.os }}-gcloud-latest-${{ steps.gcloud_latest_hash.outputs.hash }}
- uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ vars.GCP_PROJECT_ID }}
setup-gcloud find version via bestVersion (https://github.com/google-github-actions/setup-cloud-sdk/blob/777fafd35762801b0926d103cf4982128ecf8f38/src/index.ts#L270-L289), which downloads the version list from versionsURL: https://github.com/google-github-actions/setup-cloud-sdk/blob/777fafd35762801b0926d103cf4982128ecf8f38/src/index.ts#L35C7-L35C18. To cache latest version, and invalidate cache when new version released, you need to use this version list as cache key.
We also see installation of gcloud via setup-cloud-sdk consistently take 3m or more inside the default GitHub Actions instances. We have been using an old version of the action -- v0.2.1 -- but will try upgrading to latest moving forward.
From what I can tell, setup-cloud-sdk populates the toolCache in installGcloudSdk: https://github.com/google-github-actions/setup-cloud-sdk/blob/370c0eedea4542439152ecedc93bb3ae3e41c0f3/src/index.ts#L183-L188
It additionally contains a method -- isInstalled -- to consult the toolCache for a previously installed version: https://github.com/google-github-actions/setup-cloud-sdk/blob/370c0eedea4542439152ecedc93bb3ae3e41c0f3/src/index.ts#L43-L57
But I don't see anywhere that calls isInstalled in the setup-cloud-sdk code or in the setup-gcloud code any more. The older versions of setup-gcloud (like the one we've been using) actually used isInstalled: https://github.com/google-github-actions/setup-gcloud/blob/daadedc81d5f9d3c06d2c92f49202a3cc2b919ba/setup-gcloud/src/setup-gcloud.ts#L35-L41
Instead the google-cloud-actions/setup-gcloud action uses toolCache.find directly to shortcut the installation: https://github.com/google-github-actions/setup-gcloud/blob/77e7a554d41e2ee56fc945c52dfd3f33d12def9a/src/main.ts#L87-L94
But setup-gcloud is calculating the version for toolCache.find differently than setup-cloud-sdk does, only using setup-cloud-sdk:bestVersion when the version provided to the action is latest: https://github.com/google-github-actions/setup-gcloud/blob/77e7a554d41e2ee56fc945c52dfd3f33d12def9a/src/main.ts#L73-L88
And setup-cloud-sdk:installGcloudSdk is using bestVersion when the supplied version spec is not an explicit version number: https://github.com/google-github-actions/setup-cloud-sdk/blob/0f7131d93a0b34fcd6831fcd58a14ac92efe03af/src/index.ts#L169-L175
The bestVersion function converts the version spec to an explicit resolvedVersion, i.e. X.Y.Z: https://github.com/google-github-actions/setup-cloud-sdk/blob/0f7131d93a0b34fcd6831fcd58a14ac92efe03af/src/index.ts#L263-L270
And that explicit resolvedVersion is what gets passed to toolCache.cacheDir: https://github.com/google-github-actions/setup-cloud-sdk/blob/0f7131d93a0b34fcd6831fcd58a14ac92efe03af/src/index.ts#L183-L188
Because setup-gcloud is using a different mechanism than setup-cloud-sdk to determine the version tag passed to toolCache.find / toolCache.cacheDir, I believe that this would result in a cache-miss for EVERY version, unless you're passing latest as the version to setup-gcloud.
This could be a lot easier to debug if any of the actions supported a verbose mode that logged these decisions, and it would be better for setup-cloud-sdk to either expose it's resolvedVersion logic OR check the toolCache itself in installGcloudSdk before installing.
From what I can tell, setup-cloud-sdk populates the toolCache in installGcloudSdk: https://github.com/google-github-actions/setup-cloud-sdk/blob/370c0eedea4542439152ecedc93bb3ae3e41c0f3/src/index.ts#L183-L188
It additionally contains a method -- isInstalled -- to consult the toolCache for a previously installed version: https://github.com/google-github-actions/setup-cloud-sdk/blob/370c0eedea4542439152ecedc93bb3ae3e41c0f3/src/index.ts#L43-L57
But I don't see anywhere that calls isInstalled in the setup-cloud-sdk code or in the setup-gcloud code any more. The older versions of setup-gcloud (like the one we've been using) actually used isInstalled:
I don't think this is accurate. It's true that setup-gcloud isn't using isInstalled, but that's because isInstalled, but it uses the same logic. Since setup-gcloud allows a version or version constraint, first the version input is pre-parsed.
- If the value for the
versioninput is the empty string (""), then we try to find any version installed in the toolcache (> 0.0.0). - If the value for the
versioninput is the literal string "latest", then we dynamically find the latest released gcloud version and set the version constraint to that resolved value.
https://github.com/google-github-actions/setup-gcloud/blob/77e7a554d41e2ee56fc945c52dfd3f33d12def9a/src/main.ts#L77-L85
At this point, the version is either "> 0.0.0", a user-supplied value, or the resolved value of the latest version. This is passed into toolCache.find, which accepts a version constraint. This is functionally equivalent to what isInstalled does, except isInstalled doesn't resolve versions. If the cache misses, then we trigger an install:
https://github.com/google-github-actions/setup-gcloud/blob/77e7a554d41e2ee56fc945c52dfd3f33d12def9a/src/main.ts#L87-L94
And setup-cloud-sdk:installGcloudSdk is using bestVersion when the supplied version spec is not an explicit version number: https://github.com/google-github-actions/setup-cloud-sdk/blob/0f7131d93a0b34fcd6831fcd58a14ac92efe03af/src/index.ts#L169-L175
The bestVersion function converts the version spec to an explicit resolvedVersion, i.e. X.Y.Z: https://github.com/google-github-actions/setup-cloud-sdk/blob/0f7131d93a0b34fcd6831fcd58a14ac92efe03af/src/index.ts#L263-L270
And that explicit resolvedVersion is what gets passed to toolCache.cacheDir: https://github.com/google-github-actions/setup-cloud-sdk/blob/0f7131d93a0b34fcd6831fcd58a14ac92efe03af/src/index.ts#L183-L188
I'm fairly certain this is all working as intended? We only intend to cache the fully-resolved versions. If today, "latest" pointed to 1.2.3, and then a new version was released wouldn't you expect it to download 1.2.4? But so long as 1.2.3 is the latest version, it would be cached.
This could be a lot easier to debug if any of the actions supported a verbose mode that logged these decisions, and it would be better for setup-cloud-sdk to either expose it's resolvedVersion logic OR check the toolCache itself in installGcloudSdk before installing.
They do - setup-gcloud has some pretty extensive debugging logging. You have to turn on GitHub Actions Debugging Logging. Note, this functionality is provided by the actions SDK (we call core.debug), and will generally work with all GitHub Actions.
I'm seeing the same behaviour as well, running v2.1.4, self-hosted runners.
- name: Install the gcloud CLI
uses: google-github-actions/[email protected]
I'm fairly certain this is all working as intended? We only intend to cache the fully-resolved versions. If today, "latest" pointed to 1.2.3, and then a new version was released wouldn't you expect it to download 1.2.4? But so long as 1.2.3 is the latest version, it would be cached.
I've not specified any versions so it should default to "latest". However, on two consecutive executions it still takes more than 45s each time.
They do -
setup-gcloudhas some pretty extensive debugging logging. You have to turn on GitHub Actions Debugging Logging. Note, this functionality is provided by the actions SDK (we callcore.debug), and will generally work with all GitHub Actions.
Also, I've enabled this as per the official documentation to try and understand why, but get no additional logs at the step.
Is there anything else I might be missing?
@alkar
I've not specified any versions so it should default to "latest". However, on two consecutive executions it still takes more than 45s each time.
Did you check the directory: ${{ runner.tool_cache }}/gcloud where the gcloud cache would be created at? I think you can check the directory 'cause you're using self-hosted runner.
Also, I've enabled this as per the official documentation to try and understand why, but get no additional logs at the step.
That's strange. At least the log about cache hit or miss would appears, if you correctly enabled the debug log settings.
This ended up working pretty great for me. I was able to also work around my problem in #714 with it.
thanks @k-jun for the pointers
- name: gcloud latest hash
id: setup_gcloud
shell: bash
run: |
set -e
VERSIONS=$(curl -4 https://raw.githubusercontent.com/google-github-actions/setup-cloud-sdk/main/data/versions.json)
LATEST_VERSION=$(echo "$VERSIONS" | jq -r '.[-1]')
HASH=$(echo "$VERSIONS" | md5sum | awk '{print $1}')
echo "gcloud_version=$LATEST_VERSION" >> $GITHUB_OUTPUT
echo "hash=$HASH" >> $GITHUB_OUTPUT
- name: gcloud caching
uses: actions/cache@v4
with:
path: ${{ runner.tool_cache }}/gcloud
key: ${{ runner.os }}-gcloud-latest-${{ steps.setup_gcloud.outputs.hash }}
- name: Setup gcloud
uses: google-github-actions/setup-gcloud@v2
with:
version: ${{ steps.setup_gcloud.outputs.gcloud_version }}
Wow, that's so much better, @StephenHodgson. Thank you. With a warm cache it's down to 6s, compared to 55s without.
I tweaked it a little to add retries (and use the etag header as the cache key):
- name: gcloud latest hash
id: gcloud_version
shell: bash
run: |
set -e
curl \
--fail --silent --show-error --location \
--retry 3 \
--dump-header headers.txt \
--output versions.json \
https://raw.githubusercontent.com/google-github-actions/setup-cloud-sdk/main/data/versions.json
LATEST_VERSION=$( jq -r '.[-1]' versions.json )
# use the etag in the response as the hash key
HASH=$( grep -E '^etag: ' headers.txt | cut -d '"' -f 2 )
{
echo "gcloud_version=$LATEST_VERSION"
echo "hash=$HASH"
} | tee -a "${GITHUB_OUTPUT}"
Is it only cache-missing when the version is "latest"? I'm trying to understand why pre-computing the version is causing a cache hit.
Is it only cache-missing when the version is "latest"? I'm trying to understand why pre-computing the version is causing a cache hit.
No. I had the version pinned to 531.0.0.
Hmm - then how does the bash script help?
It seems to do a better job at determining the latest version to cache than the internal implementation, then restores that cache version. By the time the internal stuff runs it's already up to date or on the version pinned, so it skips the part that takes a long time.
Hmm something super weird is going on then. Is it the call to actions/cache that's actually speeding things up?
Okay I did some digging and benchmarking. I learned a few things:
-
actions/toolkit/tool-cachedoesn't actually cache things outside of the runner. It's a local cache, not a saved cache likeactions/cachewhich puts things in an object store. -
The most expensive operation was copying the downloaded files into that cache:
[2025-08-08T18:41:52.375Z] [DEBUG] setup-gcloud.run: start [2025-08-08T18:41:52.377Z] [DEBUG] setup-gcloud.run: checking pinning [2025-08-08T18:41:52.378Z] [DEBUG] setup-gcloud.run: finished checking pinning [2025-08-08T18:41:52.378Z] [DEBUG] setup-gcloud.run: parsing inputs [2025-08-08T18:41:52.378Z] [DEBUG] setup-gcloud.run: finished parsing inputs [2025-08-08T18:41:52.378Z] [DEBUG] setup-gcloud.run: computing version information [2025-08-08T18:41:52.378Z] [DEBUG] setup-gcloud.run: version was latest, computing [2025-08-08T18:41:52.378Z] [DEBUG] setup-gcloud.run: getting available versions [2025-08-08T18:41:52.378Z] [DEBUG] setup-cloud-sdk.getAvailableVersions: start [2025-08-08T18:41:52.379Z] [DEBUG] setup-cloud-sdk.getAvailableVersions: attempting... [2025-08-08T18:41:52.426Z] [DEBUG] setup-cloud-sdk.getAvailableVersions: got response [2025-08-08T18:41:52.427Z] [DEBUG] setup-cloud-sdk.getAvailableVersions: finish [2025-08-08T18:41:52.427Z] [DEBUG] setup-gcloud.run: finished getting available versions [2025-08-08T18:41:52.427Z] [DEBUG] setup-gcloud.run: computing best version [2025-08-08T18:41:52.427Z] [DEBUG] setup-cloud-sdk.computeBestVersion: start [2025-08-08T18:41:52.429Z] [DEBUG] setup-cloud-sdk.computeBestVersion: sorted [2025-08-08T18:41:52.431Z] [DEBUG] setup-cloud-sdk.computeBestVersion: found candidate [2025-08-08T18:41:52.431Z] [DEBUG] setup-cloud-sdk.computeBestVersion: finish [2025-08-08T18:41:52.431Z] [DEBUG] setup-gcloud.run: finished computing best version version 533.0.0 [2025-08-08T18:41:52.431Z] [DEBUG] setup-gcloud.run: check if version is in the toolcache [2025-08-08T18:41:52.432Z] [DEBUG] setup-gcloud.run: installing the SDK [2025-08-08T18:41:52.433Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: version 533.0.0 [2025-08-08T18:41:52.433Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: getting release URL [2025-08-08T18:41:52.433Z] [DEBUG] setup-cloud-sdk.buildReleaseURL: start [2025-08-08T18:41:52.433Z] [DEBUG] setup-cloud-sdk.buildReleaseURL: finish [2025-08-08T18:41:52.433Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: computed release URL https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-533.0.0-linux-x86_64.tar.gz [2025-08-08T18:41:52.433Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: downloading and extracting tool [2025-08-08T18:41:52.433Z] [DEBUG] setup-cloud-sdk.downloadAndExtractTool: start [2025-08-08T18:41:52.433Z] [DEBUG] setup-cloud-sdk.downloadTool: start [2025-08-08T18:41:53.877Z] [DEBUG] setup-cloud-sdk.downloadTool: finish /usr/bin/tar xz --warning=no-unknown-keyword --overwrite -C /home/runner/work/_temp/95fd0491-c00f-487d-ac7d-dbf91ecb3af9 -f /home/runner/work/_temp/e65e9f5d-7997-4822-8e20-be1d3a984be3 [2025-08-08T18:41:58.469Z] [DEBUG] setup-cloud-sdk.downloadAndExtractTool: finish [2025-08-08T18:41:58.469Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: finished downloading and extracting tool [2025-08-08T18:41:58.469Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: caching directory [2025-08-08T18:42:12.411Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: finished caching directory [2025-08-08T18:42:12.411Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: adding to path= [2025-08-08T18:42:12.411Z] [DEBUG] setup-cloud-sdk.installGcloudSDK: finished adding to path [2025-08-08T18:42:12.411Z] [DEBUG] setup-gcloud.run: finished installing SDK [2025-08-08T18:42:12.411Z] [DEBUG] setup-gcloud.run: checking if authenticated [2025-08-08T18:42:12.412Z] [DEBUG] setup-cloud-sdk.isAuthenticated: start [2025-08-08T18:42:12.412Z] [DEBUG] setup-cloud-sdk.gcloudRun: cmd [ 'auth', 'list' ] [2025-08-08T18:42:12.413Z] [DEBUG] setup-cloud-sdk.gcloudRun: getExecOutput start [2025-08-08T18:42:15.321Z] [DEBUG] setup-cloud-sdk.gcloudRun: getExecOutput finish [2025-08-08T18:42:15.321Z] [DEBUG] setup-cloud-sdk.isAuthenticated: finish [2025-08-08T18:42:15.321Z] [DEBUG] setup-gcloud.run: finished checking if authenticated -
It's genuinely faster, on all operating systems, if we don't move the installation to the tool-cache:
-
Before:
-
After:
-
-
Installing components is slow, no matter which installation method is used.
So I think the best path forward is to ironically to disable the cache, which seems to consistently deliver fast installation times.
FWIW, the delta in time for the "~6s" above vs the "~10s" I'm seeing is around activating authentication.
There's a new option skip_tool_cache. For backwards-compat, it defaults to false. Setting it to true will dramatically improve installation times, and that will be the default in the next major release.
Thank you for looking into this 🙏
If I understood correctly, in order to have faster installation times, we need to set skip_tool_cache: true? 🤯 Future users are going to have an interesting time figuring that out 😅
@javiercr for now, yes. We can change the default when we release the next major version (along with other breaking changes), but for now, we have to keep compat with the existing releases.