Skip to main content
Moon is able to achieve high performance and blazing speeds by implementing a cache that’s powered by our own unique smart hashing layer. All cache is stored in .moon/cache, relative from the workspace root (be sure to git ignore this folder).

Overview

The caching system consists of two main components:
  1. Hashing - Determines if a task needs to re-run
  2. Archiving - Stores and restores task outputs
Caching is automatic and enabled by default for all tasks. You can disable it per-task with options.cache: false.

Hashing

Incremental builds are possible through a concept known as hashing, where in multiple sources are aggregated to generate a unique hash. In the context of moon, each time a target is ran we generate a hash, and if this hash already exists we abort early (cache hit), otherwise we continue the run (cache miss). The tiniest change may trigger a different hash, for example, changing a line of code (when an input), or updating a package version, so don’t worry if you see a lot of hashes.

Hash Sources

Our smart hashing currently takes the following sources into account:
  • Command (command) being ran and its arguments (args)
  • Input sources (inputs)
  • Output targets (outputs)
  • Environment variables (env)
  • Dependencies between projects (dependsOn) and tasks (deps)
For Deno tasks:
  • Deno version
  • deno.json/deps.ts imports, import maps, and scopes
  • tsconfig.json compiler options (when applicable)
For Bun and Node.js tasks:
  • Bun/Node.js version
  • package.json dependencies (including development and peer)
  • tsconfig.json compiler options (when applicable)
For Rust tasks:
  • Rust version
  • Cargo.toml and Cargo.lock contents

Hash Generation Example

moon.yml
tasks:
  build:
    command: 'webpack'
    args:
      - '--mode'
      - 'production'
    inputs:
      - 'src/**/*.ts'
      - 'webpack.config.js'
      - 'package.json'
    outputs:
      - 'dist'
    env:
      NODE_ENV: 'production'
This generates a hash based on:
  • Command: webpack --mode production
  • All files matching src/**/*.ts
  • Contents of webpack.config.js and package.json
  • Output directory: dist
  • Environment variable: NODE_ENV=production
  • Node.js version from toolchain
  • Dependencies from package.json
Be aware that greedy inputs (**/*, the default) will include everything in the target directory as a source. We do our best to filter out VCS ignored files, and outputs for the current task, but files may slip through that you don’t expect. We suggest using explicit inputs and routinely auditing the hash files for accuracy!

Cache Hit vs Cache Miss

Cache Hit:
$ moon run app:build

app:build | cached in .moon/cache/outputs/abc123def456.tar.gz
Cache Miss:
$ moon run app:build

app:build | running command: webpack --mode production
app:build | asset main.js 1.2 MiB [emitted]
app:build | completed in 3.5s

Hash Inspection

Inspect hash contents for debugging:
# View hash manifest
cat .moon/cache/hashes/abc123def456.json
{
  "command": "webpack --mode production",
  "args": ["--mode", "production"],
  "inputs": [
    "src/index.ts:hash123",
    "src/app.ts:hash456",
    "webpack.config.js:hash789"
  ],
  "env": {
    "NODE_ENV": "production"
  },
  "platform": {
    "node": "20.0.0"
  },
  "dependencies": {
    "react": "18.2.0",
    "webpack": "5.88.0"
  }
}

Archiving & Hydration

On top of our hashing layer, we have another concept known as archiving, where in we create a tarball archive of a task’s outputs and store it in .moon/cache/outputs. These are akin to build artifacts.

How It Works

  1. Task Runs - Command executes and produces outputs
  2. Archiving - Outputs are compressed into .tar.gz
  3. Storage - Archive stored with hash as filename
  4. Hydration - On cache hit, archive is unpacked to restore outputs
.moon/cache/outputs/
├── abc123def456.tar.gz  # First build
├── def789ghi012.tar.gz  # After code change
└── ghi345jkl678.tar.gz  # After dependency update

Hydration Process

When we encounter a cache hit on a hash, we trigger a mechanism known as hydration, where we efficiently unpack an existing tarball archive into a task’s outputs. This can be understood as a timeline, where every point in time will have its own hash + archive that moon can play back.

Smart Skipping

Furthermore, if we receive a cache hit on the hash, and the hash is the same as the last run, and outputs exist, we exit early without hydrating and assume the project is already hydrated. In the terminal, you’ll see a message for “cached”.
$ moon run app:build
app:build | cached

$ moon run app:build
app:build | cached  # Outputs already exist, no hydration needed

File Structure

The following diagram outlines our cache folder structure and why each piece exists:
.moon/cache/
  # Stores hash manifests of every ran task. Exists purely for debugging purposes.
  hashes/
    # Contents includes all sources used to generate the hash.
    <hash>.json

  # File system locks for parallel processes.
  locks/
    <file>.lock

  # Stores `tar.gz` archives of a task's outputs based on its generated hash.
  outputs/
    <hash>.tar.gz

  # JSON schemas for validating configuration files.
  schemas/
    <schema>.json

  # State information about anything and everything within moon. Toolchain,
  # dependencies, projects, running targets, etc.
  states/
    # Files at the root pertain to the entire workspace.
    <state>.json

    # Files for a project are nested within a folder by the project name.
    <project>/
      # Informational snapshot of the project, its tasks, and its configs.
      # Can be used at runtime by tasks that require this information.
      snapshot.json

      <task>/
        # Contents of the child process, including the exit code and
        # unique hash that is referenced above.
        lastRun.json

        # Outputs of last run target.
        stderr.log
        stdout.log

State Files

Moon stores various state information:
.moon/cache/states/
├── toolchainState.json      # Installed tool versions
├── dependenciesState.json   # Dependency graph state
├── projectState.json        # Project graph state
└── app/
    ├── snapshot.json        # Project metadata
    └── build/
        ├── lastRun.json     # Last execution details
        ├── stdout.log       # Build output
        └── stderr.log       # Error output

Cache Configuration

Per-Task Cache Control

moon.yml
tasks:
  # Caching enabled (default)
  build:
    command: 'webpack'
    outputs:
      - 'dist'
  
  # Caching disabled
  dev:
    command: 'vite'
    preset: 'server'
    options:
      cache: false  # Never cache
  
  # Conditional caching
  test:
    command: 'jest'
    options:
      cache: true
      runInCI: true

Output Specifications

Be explicit about outputs for optimal caching:
moon.yml
tasks:
  build:
    command: 'tsc'
    outputs:
      - 'dist'           # Directory
      - 'lib/**/*.js'    # Glob pattern
      - 'build/main.js'  # Specific file
If outputs is not specified, moon won’t create an archive, but will still use hashing to determine if the task should run.

Remote Caching

Share cache artifacts across machines and CI environments:
.moon/workspace.yml
remote:
  host: 'grpcs://cache.example.com'
  auth:
    token: 'CACHE_TOKEN'
  cache:
    verifyIntegrity: true
    compression: 'zstd'
    readonly: false

Benefits of Remote Caching

  • Share builds across team members
  • Faster CI by reusing local builds
  • Consistent artifacts across environments
  • Reduced compute costs in CI/CD
# First developer builds
$ moon run app:build
app:build | completed in 30s
app:build | uploading to remote cache

# Second developer (or CI) gets instant result
$ moon run app:build
app:build | downloading from remote cache
app:build | cached in 2s

Cache Management

Clearing Cache

# Clear all cache
rm -rf .moon/cache

# Clear specific project cache
rm -rf .moon/cache/states/app

# Clear output archives
rm -rf .moon/cache/outputs

# Clear hash manifests
rm -rf .moon/cache/hashes

Cache Debugging

When tasks aren’t caching as expected:
# View last run information
cat .moon/cache/states/app/build/lastRun.json

# View hash manifest
cat .moon/cache/hashes/<hash>.json

# Check output logs
cat .moon/cache/states/app/build/stdout.log
cat .moon/cache/states/app/build/stderr.log

# Run with verbose logging
moon run app:build --log trace

Performance Tips

Avoid **/* inputs when possible. Explicitly list input files and patterns to reduce hash calculation time and prevent unexpected cache invalidation.
inputs:
  - 'src/**/*.ts'  # Good: specific pattern
  - 'package.json'
  - '**/*'          # Avoid: includes everything
Only include actual output files/directories. This reduces archive size and speeds up hydration.
outputs:
  - 'dist'          # Good: specific output dir
  - '.'             # Avoid: includes source files
Enable remote caching for teams and CI. The initial setup pays off quickly with shared build artifacts.
Periodically clean old cache entries. Consider adding a cleanup script to CI:
# Remove cache entries older than 30 days
find .moon/cache/outputs -mtime +30 -delete

Best Practices

Always add .moon/cache/ to .gitignore. Cache should be generated locally or pulled from remote cache.
.gitignore
.moon/cache/
Ensure all team members and CI use the same tool versions (via .moon/toolchains.yml) for consistent cache hits.
Periodically review hash manifests to ensure only relevant files are included as inputs.
  • Tasks - Define inputs and outputs for caching
  • Targets - Run cached tasks
  • Affected - Smart task execution based on changes

Configuration Reference

For detailed configuration options, see:

Build docs developers (and LLMs) love