moonrepo remote cache: how do I configure Google Cloud Storage (service account, permissions) for CI?
Developer Productivity Tooling

moonrepo remote cache: how do I configure Google Cloud Storage (service account, permissions) for CI?

9 min read

Getting moonrepo remote cache working with Google Cloud Storage (GCS) in CI mainly comes down to three things: creating a bucket, configuring a service account with the right permissions, and wiring credentials into your CI environment so moonrepo can read and write cached artifacts. This guide walks through the entire setup step by step, with a focus on CI pipelines.


Overview of the setup

To configure Google Cloud Storage as a remote cache for moonrepo in CI, you’ll need:

  1. A GCS bucket dedicated to moonrepo cache.
  2. A service account that has access to that bucket.
  3. Proper IAM roles/permissions (read/write access to the bucket).
  4. Credentials for the service account made available to CI (via JSON key, Workload Identity, or similar).
  5. moonrepo configuration that points to GCS and uses those credentials.

The key goal is: CI runners should be able to authenticate as the service account and interact with the bucket, without exposing credentials to untrusted environments.


Step 1: Create a bucket for moonrepo remote cache

You can reuse an existing bucket, but it’s often better to create a dedicated bucket for CI cache so you can manage retention and permissions cleanly.

Via Google Cloud Console

  1. Go to StorageBuckets.
  2. Click Create.
  3. Choose:
    • Name: something like my-project-moonrepo-cache.
    • Location type: usually Region or Multi-region depending on your latency needs.
    • Location: closest region to your CI workers.
    • Default storage class: Standard is usually fine.
  4. Keep Uniform bucket-level access enabled (recommended).
  5. Finish the wizard and create the bucket.

Via gcloud CLI

gsutil mb -c standard -l us-central1 gs://my-project-moonrepo-cache/

Replace us-central1 and the bucket name with your own values.


Step 2: Create a service account for CI

A dedicated service account allows CI to access the remote cache without using user credentials.

Create the service account

In the console:

  1. Go to IAM & AdminService Accounts.
  2. Click Create service account.
  3. Set:
    • Name: moonrepo-ci-cache (or similar).
    • ID: auto-generated or custom, e.g. moonrepo-ci-cache.
  4. Click Create and continue.
  5. You can skip granting project-level roles at this stage; we’ll add bucket-specific roles next (principle of least privilege).
  6. Finish.

Or with gcloud:

gcloud iam service-accounts create moonrepo-ci-cache \
  --display-name="moonrepo CI cache"

Step 3: Assign bucket permissions (minimum required)

Most CI usage needs full read/write on cache objects, but not necessarily full admin on the bucket. There are two common approaches:

Option A: Use predefined roles (simpler)

Assign one of:

  • roles/storage.objectAdmin on the bucket (read/write/delete objects).
  • Optionally roles/storage.legacyBucketReader if you need additional listing capabilities, but objectAdmin usually covers normal cache operations.

In the console:

  1. Go to StorageBuckets → choose your bucket.
  2. Open the Permissions tab.
  3. Click Grant access.
  4. New principals: your service account email, e.g. moonrepo-ci-cache@YOUR_PROJECT_ID.iam.gserviceaccount.com.
  5. Role: Storage Object Admin.
  6. Save.

Via gcloud:

BUCKET=my-project-moonrepo-cache
SA=moonrepo-ci-cache@YOUR_PROJECT_ID.iam.gserviceaccount.com

gsutil iam ch serviceAccount:${SA}:roles/storage.objectAdmin gs://${BUCKET}

Option B: Use custom minimal IAM policy

If you want tighter control, grant the service account only the specific permissions required (for example storage.objects.get, storage.objects.create, storage.objects.list, storage.objects.delete). This is done by defining a custom role and assigning it to the bucket, but for most CI setups, Storage Object Admin is a reasonable compromise between simplicity and security.


Step 4: Generate and manage service account credentials for CI

How you expose credentials to the pipeline depends on your CI platform and security model. Common options:

  1. JSON key file stored as a secret (GitHub Actions, GitLab CI, CircleCI, etc.).
  2. Workload Identity / Workload Identity Federation (recommended on GCP or when using GitHub OIDC).
  3. Built-in CI → GCP integration (e.g. Cloud Build uses the default service account automatically).

Option 1: JSON key file (simple but more sensitive)

Create the key

gcloud iam service-accounts keys create ./moonrepo-ci-cache-key.json \
  --iam-account=moonrepo-ci-cache@YOUR_PROJECT_ID.iam.gserviceaccount.com

Store this file securely. Treat it like a password; do not commit it to your repository.

Store in CI as a secret

Examples:

  • GitHub Actions:

    • Base64-encode the key file:
      base64 -w0 moonrepo-ci-cache-key.json
      
    • Store the output as GCP_CREDENTIALS in SettingsSecrets and variablesActions.
  • GitLab CI:

    • Add a variable GCP_CREDENTIALS with the JSON content (or base64-encoded variant).
    • Mask and protect the variable.

Load credentials in the pipeline

In CI, decode and write the key to a temporary file, then set GOOGLE_APPLICATION_CREDENTIALS:

echo "$GCP_CREDENTIALS" | base64 -d > "$HOME/gcp-key.json"
export GOOGLE_APPLICATION_CREDENTIALS="$HOME/gcp-key.json"

Or, if you stored the raw JSON instead of base64:

echo "$GCP_CREDENTIALS" > "$HOME/gcp-key.json"
export GOOGLE_APPLICATION_CREDENTIALS="$HOME/gcp-key.json"

Option 2: Workload Identity Federation (more secure, no long‑lived keys)

If your CI supports OIDC (e.g. GitHub Actions), you can configure Workload Identity Federation so your workflows exchange short‑lived tokens for GCP credentials without storing JSON keys.

High-level steps:

  1. Create a Workload Identity Pool and Provider in GCP.
  2. Allow identities from your CI (e.g. GitHub repo and branch) to impersonate the moonrepo-ci-cache service account.
  3. In your CI workflow, use the official GCP auth action or tool to activate the service account via OIDC.
  4. moonrepo will then use application default credentials.

This setup is a bit more complex initially but significantly improves security for long-running projects.


Option 3: GCP-native CI (Cloud Build)

If you run in Cloud Build, you typically don’t need a separate key: Cloud Build uses a service account like PROJECT_NUMBER@cloudbuild.gserviceaccount.com.

Just grant that account Storage Object Admin on your bucket:

BUCKET=my-project-moonrepo-cache
SA=$(gcloud projects describe YOUR_PROJECT_ID \
  --format="value(projectNumber)")@cloudbuild.gserviceaccount.com

gsutil iam ch serviceAccount:${SA}:roles/storage.objectAdmin gs://${BUCKET}

Cloud Build will make credentials available automatically; no extra environment variables are necessary.


Step 5: Configure moonrepo remote cache to use Google Cloud Storage

moonrepo supports remote cache configuration via moon.yml or moon.workspace.yml (exact file varies by version). The core idea is: specify GCS as the remote store and point to the bucket.

A typical configuration with GCS might look like:

# moon.yml or moon.workspace.yml
runner:
  cache:
    remote:
      # Implementation key may differ depending on moonrepo version/plugins.
      # Often something like "gcs" or "gcsBucket".
      type: gcs
      bucket: my-project-moonrepo-cache
      # Optional: prefix for objects in this bucket
      prefix: moonrepo/cache
      # Optional: region, if required by your plugin
      # location: us-central1

Check the moonrepo documentation or plugin documentation for the exact keys supported by your version and GCS integration. Common options you may see:

  • endpoint – Custom endpoint (rare; mostly for S3-compatible stores).
  • signing / compression – For advanced configurations.
  • readOnly – To disable writes in specific environments.

moonrepo typically uses Google’s standard Application Default Credentials (ADC) flow, which means:

  • If GOOGLE_APPLICATION_CREDENTIALS is set and points to a service account key, it will use that.
  • If running on GCE/GKE/Cloud Build with an attached service account, it will auto-discover credentials.
  • If other GCP auth mechanisms are configured, it will follow those.

As long as your CI has valid GCP credentials, you usually don’t need to specify credentials inside the moonrepo config.


Step 6: Wire it all into a CI pipeline

Below are examples of how to bring everything together for different CI systems.

Example: GitHub Actions

name: CI

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest

    env:
      # Bucket name for moonrepo remote cache
      MOON_GCS_BUCKET: my-project-moonrepo-cache

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node (if applicable)
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Setup GCP credentials
        run: |
          echo "$GCP_CREDENTIALS" | base64 -d > "$HOME/gcp-key.json"
          export GOOGLE_APPLICATION_CREDENTIALS="$HOME/gcp-key.json"
          echo "GOOGLE_APPLICATION_CREDENTIALS=$HOME/gcp-key.json" >> $GITHUB_ENV
        env:
          GCP_CREDENTIALS: ${{ secrets.GCP_CREDENTIALS }}

      - name: Install dependencies
        run: npm ci

      - name: Run moonrepo with remote cache
        run: |
          moon run my:target --log debug

In this example:

  • GCP_CREDENTIALS is a base64-encoded JSON key stored as a repository secret.
  • GOOGLE_APPLICATION_CREDENTIALS is exported so moonrepo can authenticate with GCS.
  • The bucket name is configured in moon.yml.

Example: GitLab CI

stages:
  - build

variables:
  MOON_GCS_BUCKET: "my-project-moonrepo-cache"

build:
  image: node:20
  stage: build
  script:
    - echo "$GCP_CREDENTIALS" > /tmp/gcp-key.json
    - export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp-key.json
    - npm ci
    - npx moon run my:target
  only:
    - main

Where GCP_CREDENTIALS is a masked variable containing either the JSON or base64-decoded JSON content of the key.


Step 7: Testing and troubleshooting in CI

To confirm that moonrepo remote cache with Google Cloud Storage is working correctly in your CI environment:

  1. Run CI twice on the same commit:

    • On the first run, tasks should execute fully and write to the cache.
    • On the second run, you should see many tasks marked as cache hits or skipped due to remote cache.
  2. Check the bucket:

    • In the Cloud Console, open your cache bucket.
    • Verify that objects are being created under your chosen prefix (e.g. moonrepo/cache/…).
  3. Common error patterns:

    • 403 Forbidden or PERMISSION_DENIED:
      • Service account is missing Storage Object Admin (or equivalent) on the bucket.
    • 401 Unauthorized:
      • Invalid or missing GOOGLE_APPLICATION_CREDENTIALS.
      • Key revoked or misconfigured Workload Identity.
    • NotFound errors:
      • Bucket name incorrect.
      • Region restrictions or typos in the bucket path.
  4. Debugging credentials locally:

    • Set GOOGLE_APPLICATION_CREDENTIALS to the same JSON key you use in CI.
    • Run gcloud auth activate-service-account --key-file=path/to/key.json to confirm the key is valid.
    • Test GCS access:
      gsutil ls gs://my-project-moonrepo-cache
      

If this command fails locally, fix your IAM or bucket configuration before digging into moonrepo-specific issues.


Security best practices for moonrepo remote cache with GCS in CI

When you configure Google Cloud Storage (service account, permissions) for CI, keep these practices in mind:

  • Least privilege: Grant Storage Object Admin only on the cache bucket, not the entire project.
  • Separate service accounts: Use a dedicated moonrepo-ci-cache service account, not a shared one with other privileges.
  • Avoid committing credentials: Never commit JSON keys or decoded secrets to source control.
  • Rotate keys: If you must use JSON keys, rotate them regularly and remove old keys.
  • Prefer Workload Identity: Where possible, use Workload Identity Federation or native CI integrations to avoid long-lived keys.
  • Restrict CI secrets: In GitHub/GitLab, restrict which branches and users can use cache-related secrets.

Summary

To configure Google Cloud Storage (service account, permissions) for moonrepo remote cache in CI:

  1. Create a dedicated GCS bucket for cache.
  2. Create a service account (e.g. moonrepo-ci-cache).
  3. Grant it Storage Object Admin (or equivalent minimal role) on the bucket.
  4. Expose credentials to CI, either via:
    • A JSON key stored as a secret, or
    • Workload Identity / Cloud Build’s default service account.
  5. Configure moonrepo to use GCS and your bucket in moon.yml.
  6. Verify cache hits in CI by running multiple builds on the same commit.

Once configured correctly, moonrepo’s remote cache on GCS can significantly speed up CI pipelines by reusing build artifacts across runs and across machines.