
moonrepo remote cache on S3: example config + IAM policy, and how do we keep it safe in CI?
Using moonrepo with a remote cache on Amazon S3 is a powerful way to speed up builds across developer machines and CI, but it raises a few practical questions: what does the S3 configuration look like, what IAM permissions are required, and how do you keep access safe in CI? This guide walks through a complete example: moonrepo config, S3 bucket setup, minimal IAM policy, and security patterns for CI systems.
How moonrepo remote caching works (quick overview)
Moonrepo’s remote cache stores build artifacts keyed by inputs (source, environment, dependencies). When remote caching is enabled:
- Local runs can read from the cache (to reuse previous artifacts).
- CI and other builders can write and read to share results.
- The cache backend is pluggable: you can use S3, GCS, etc.
For S3 specifically, moonrepo:
- Writes objects (artifacts) into a bucket, typically under a prefix like
moon-cache/. - Reads objects by key when a matching cache entry exists.
- Uses AWS credentials from the standard SDK chain (environment vars, role, profile, etc.).
Example moonrepo config for S3 remote cache
You configure remote caching in your Moon workspace configuration. Depending on your setup, this will usually be in .moon/workspace.yml or moon.yml.
Below is a realistic example for an S3-backed remote cache.
Basic S3 remote cache configuration
# .moon/workspace.yml
tasks:
# Enable the global tasks cache
cache:
engine: "s3"
options:
# Required: name of your cache bucket
bucket: "my-company-moon-cache-prod"
# Optional: path prefix inside the bucket
# (helps organize / segregate environments or monorepos)
prefix: "moon-cache/main-repo"
# Optional: region, if you want to override env / AWS default
region: "us-east-1"
# Optional: whether to compress artifacts before uploading
compression: "gzip"
# Optional: tune TTL behavior if moon supports your version
# maxAge: 604800 # cache TTL in seconds (example, 7 days)
# Control remote caching behavior
remote:
read: true # allow reading from remote cache
write: true # allow writing to remote cache
# Optional: You can further override cache behavior per project if needed.
Key points:
engine: "s3"tells moonrepo to use S3 as the remote backend.bucketis the S3 bucket with your cache objects.prefixhelps you separate environments, repos, or branches.remote.readandremote.writecan be toggled per environment (more on that below).
Example: restricting writes locally, but allowing read in CI
You might want local developers to read from CI’s cache but not write to the shared S3 cache (to avoid polluted cache entries).
You can do this by:
- Defaulting to
read: true, write: falsein config. - Overriding with
write: truein CI via env vars or a CI-specific config.
For example, using environment variable overrides (exact variable names may differ by moon version; adjust accordingly to official docs):
# In local dev, no env vars:
export MOON_CACHE_REMOTE_READ=true
export MOON_CACHE_REMOTE_WRITE=false
# In CI job config (GitHub Actions example):
env:
MOON_CACHE_REMOTE_READ: "true"
MOON_CACHE_REMOTE_WRITE: "true"
Or, you can keep your base workspace config read-only and use a CI-specific config file merged via your CI pipeline.
S3 bucket setup for moonrepo remote cache
Create an S3 bucket dedicated to moonrepo cache:
- Name example:
my-company-moon-cache-prod - Block all public access.
- Enable default encryption (SSE-S3 or SSE-KMS).
- Turn on lifecycle rules to expire old cache objects.
Example S3 bucket settings
- Block Public Access: enabled (all four options).
- Bucket Policy: no public
Principal(*) allow statements. - Encryption:
- SSE-S3 (AES-256) or SSE-KMS with a dedicated KMS key.
- Lifecycle Rules:
- Transition to cheaper storage after some days (optional).
- Expire after 7–30 days depending on cache volume and costs.
Example lifecycle rule (conceptual):
- Rule name:
moon-cache-expire - Filter: prefix
moon-cache/ - Expiration: 14 days after creation.
Minimal IAM policy for moonrepo remote cache on S3
To keep the cache safe, you want the principle of least privilege:
- One IAM role or user per environment (e.g.,
moon-cache-ci,moon-cache-dev). - Each role has access only to:
- The cache bucket you specify.
- A specific prefix if you need separation (e.g.,
moon-cache/{env}/).
Basic S3 cache IAM policy (read & write)
This example grants read/write access only to objects under a specific prefix. Adjust ACCOUNT_ID, REGION, BUCKET_NAME, and PREFIX for your environment.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucketPrefix",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::BUCKET_NAME",
"Condition": {
"StringLike": {
"s3:prefix": [
"moon-cache/main-repo/*"
]
}
}
},
{
"Sid": "ReadWriteCacheObjects",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::BUCKET_NAME/moon-cache/main-repo/*"
}
]
}
Notes:
ListBucketis restricted to objects with prefixmoon-cache/main-repo/.- Only
GetObject,PutObject, andDeleteObjectare allowed. - No permission to change bucket policy, ACLs, or other buckets.
If you want read-only access (for some environments), remove s3:PutObject and s3:DeleteObject.
Optional: KMS key policy for SSE-KMS
If you use SSE-KMS (recommended for sensitive repos), your builders must have permissions on the KMS key.
Attach a KMS key policy or IAM policy like:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowUseOfKeyForMoonCache",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_ID:role/moon-cache-ci"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}
Then configure your bucket’s default encryption to use this KMS key.
How to authenticate moonrepo to S3
Moonrepo depends on the standard AWS credentials chain. You don’t configure keys inside moon itself; instead, you configure the environment:
- AWS access key pair:
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY- Optionally
AWS_SESSION_TOKENif using temporary credentials.
- IAM role:
- For EC2, ECS, Lambda, or CI via OIDC.
- AWS profile:
AWS_PROFILEpointing to~/.aws/credentials.
Prefer roles and short-lived credentials over long-lived access keys, especially for CI.
Local dev authentication
Options:
- AWS SSO / IAM Identity Center:
- Use
aws configure ssoandAWS_PROFILE=moon-devwhen running moon.
- Use
- Named profile with access keys:
- Store in
~/.aws/credentials. - Export
AWS_PROFILE=moon-dev.
- Store in
- Env vars:
- Export access keys locally only for test environments.
Keep production cache credentials separate from dev.
CI authentication
How you authenticate depends on your CI provider:
- GitHub Actions:
- Use OIDC +
aws-actions/configure-aws-credentials.
- Use OIDC +
- GitLab CI:
- Use an IAM role with web identity or access keys in CI variables.
- CircleCI, Buildkite, etc.:
- Typically: attach an IAM role to runners or inject short-lived credentials.
Example for GitHub Actions is below.
Secure CI setup for moonrepo S3 cache
The main concerns for “how do we keep it safe in CI?”:
- Avoid hard-coded AWS keys in CI config.
- Limit privilege to only the cache bucket/prefix.
- Use short-lived credentials.
- Prevent unauthorized jobs/environments from hitting the same cache.
- Avoid leaking cache contents across forks and pull requests.
Example: GitHub Actions + S3 remote cache with OIDC
Step 1: Create IAM role for GitHub Actions
Create a role like my-company-moon-cache-ci that the GitHub OIDC provider can assume.
Example trust policy (simplified; restrict to your repo + branch):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:ORG/REPO:*"
}
}
}
]
}
Attach the S3 cache IAM policy discussed earlier to this role.
Step 2: Configure GitHub Actions workflow
name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write # required for OIDC
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials for moon cache
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/my-company-moon-cache-ci
aws-region: us-east-1
- name: Setup Node (if needed)
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Enable moon remote cache write in CI
run: |
echo "MOON_CACHE_REMOTE_READ=true" >> $GITHUB_ENV
echo "MOON_CACHE_REMOTE_WRITE=true" >> $GITHUB_ENV
- name: Run moon tasks
run: |
npx moon run :test
npx moon run :build
Highlights:
- Credentials are short-lived, issued per job through OIDC.
- The role scopes access to the cache bucket/prefix only.
- Environment variables toggle moon remote read/write behavior.
Handling pull requests securely
Consider the following:
- Incoming PRs from forks:
- Often you should not give them access to prod cache or AWS at all.
- Use conditional steps:
- For
pull_requestfrom forks: disable remote cache or use a separate low-privilege cache.
- For
- Branch-specific cache namespaces:
- Use S3 prefix per branch or per environment:
moon-cache/main/moon-cache/feature/*
- Or let moon incorporate branch in cache key if supported.
- Use S3 prefix per branch or per environment:
Example GitHub Actions conditional:
- name: Configure AWS credentials
if: github.event.pull_request.head.repo.fork == false
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/my-company-moon-cache-ci
aws-region: us-east-1
For forked PRs, skip this step and rely on local cache only.
Additional hardening tips for S3 remote caches
To keep your moonrepo remote cache on S3 as safe as possible:
1. Use private buckets only
- Block public access at both the S3 account and bucket level.
- Do not use bucket policies that allow
Principal: "*". - Review the bucket policy regularly.
2. Use encryption everywhere
- Default S3 encryption:
- SSE-S3 is acceptable for most builds.
- Use SSE-KMS for extra control/auditing or stricter compliance.
- Ensure your IAM roles have access to the KMS key if you use SSE-KMS.
3. Lifecycle policies to reduce data exposure
- Old cache entries are not usually needed forever.
- Expire them after 7–30 days to reduce:
- Cost.
- Risk of very old artifacts lingering in storage.
4. Separate dev, staging, and production caches
Use either:
- Separate buckets:
moon-cache-dev,moon-cache-staging,moon-cache-prod. - Or separate prefixes and IAM roles with limited access.
Example prefixes:
moon-cache/dev/main-repo/moon-cache/staging/main-repo/moon-cache/prod/main-repo/
Your CI role for prod should not have access to dev, and vice versa.
5. Avoid embedding secrets in cache contents
Moon artifacts should not contain credentials or secrets. Double-check:
- Build output does not package
.envfiles with secrets. - Docker images are built separately, not stored raw in cache.
- Secrets are injected at runtime, not baked into cacheable artifacts.
6. Limit IAM roles to CI only
- For GitHub Actions, use OIDC roles that are only assumable by your production repo.
- For self-hosted runners or other CI, ensure the machine role is limited and not reused for unrelated tasks.
Putting it all together
To set up moonrepo remote cache on S3 and keep it safe in CI:
- Create a private S3 bucket for the cache (e.g.,
my-company-moon-cache-prod) with encryption and lifecycle rules. - Define a minimal IAM policy that:
- Grants
ListBucketon the cache prefix. - Grants
GetObject/PutObject/DeleteObjectonly on the cache prefix.
- Grants
- Create an IAM role per environment (e.g.,
moon-cache-ci) and attach the policy. - Use short-lived credentials:
- OIDC-based role assumption in GitHub Actions or other CI.
- Local dev via AWS SSO or a separate profile.
- Configure moonrepo:
- Set
engine: "s3",bucket, andprefixin.moon/workspace.yml. - Use
remote.read/remote.writeflags and env overrides for CI vs local.
- Set
- Harden the setup:
- Keep buckets private.
- Separate environments via buckets or prefixes.
- Disable remote cache for untrusted PRs.
- Avoid secrets in build artifacts.
With this pattern, you get fast, shared builds via moonrepo’s remote cache on S3, while keeping your cloud resources and build artifacts locked down appropriately for CI and production use.