Skip to content
C++
Analysis & Formatting
Updated 2025-01-01T00:00:00.000Z

ccache — Compiler Cache

ccache setup, configuration, cache statistics, CMake integration, Docker layer caching, and CI speedup patterns for C++ projects.

TL;DR

ccache is a transparent compiler cache. It wraps your compiler (gcc, g++, clang, clang++), hashes the preprocessed source and flags, and returns a cached object file when the hash matches — skipping compilation entirely. On a warm cache, a full rebuild can drop from minutes to seconds.

bash
# Install and enable globally in seconds
sudo apt install ccache
export CC="ccache gcc"
export CXX="ccache g++"
make -j$(nproc)   # first build populates cache; subsequent builds are fast

ccache is safe: it only returns a cached result when the hash guarantees the output would be identical. It handles macros, includes, and compiler flags correctly.


Installation

bash
# Ubuntu / Debian
sudo apt install ccache

# Fedora / RHEL
sudo dnf install ccache

# macOS (Homebrew)
brew install ccache

# Windows (vcpkg)
vcpkg install ccache

# From source (latest release)
git clone https://github.com/ccache/ccache
cd ccache && cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
sudo cmake --install build

Verify:

bash
ccache --version
# ccache version 4.x.y
which ccache        # /usr/bin/ccache or /usr/local/bin/ccache

Basic usage

Environment variables

The simplest way — set CC and CXX before invoking your build system:

bash
export CC="ccache gcc"
export CXX="ccache g++"

# Or with Clang
export CC="ccache clang"
export CXX="ccache clang++"

make -j$(nproc)

On some systems ccache installs compiler-named symlinks so you don't have to change CC/CXX:

bash
ls /usr/lib/ccache/
# gcc  g++  clang  clang++  cc  c++  ...

# Prepend the masquerade directory to PATH
export PATH="/usr/lib/ccache:$PATH"
make -j$(nproc)   # gcc now transparently goes through ccache

CMake integration

The modern CMake approach uses launcher variables. Set them in CMakePresets.json or pass them on the command line:

bash
cmake -B build \
  -DCMAKE_C_COMPILER_LAUNCHER=ccache \
  -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
  -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)

In CMakeLists.txt

cmake
# CMakeLists.txt
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    set(CMAKE_C_COMPILER_LAUNCHER   "${CCACHE_PROGRAM}")
    set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
    message(STATUS "ccache found: ${CCACHE_PROGRAM}")
else()
    message(STATUS "ccache not found — builds will not be cached")
endif()

CMakePresets.json

json
{
  "version": 6,
  "configurePresets": [
    {
      "name": "default",
      "generator": "Ninja",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "RelWithDebInfo",
        "CMAKE_C_COMPILER_LAUNCHER":   "ccache",
        "CMAKE_CXX_COMPILER_LAUNCHER": "ccache"
      }
    }
  ]
}

Configuration file

ccache looks for configuration in ~/.config/ccache/ccache.conf (user) or /etc/ccache.conf (system). Create it if it does not exist:

ini
# ~/.config/ccache/ccache.conf

# Maximum cache size (K, M, G, T suffixes supported)
max_size = 10G

# Where to store the cache (default: ~/.cache/ccache)
cache_dir = /home/alice/.cache/ccache

# Compress cached object files (saves ~40% disk; negligible CPU cost)
compression = true
compression_level = 6

# Log file for debugging cache misses
# log_file = /tmp/ccache.log

# Hash the compiler's actual binary so different compiler versions
# get separate cache entries
compiler_check = %compiler_hash%

# Sloppiness options — see the "Direct vs preprocessor mode" section
# sloppiness = include_file_mtime,pch_defines

Changes take effect immediately — no restart needed.

Setting config via the command line

bash
ccache --set-config max_size=20G
ccache --set-config compression=true
ccache --get-config max_size   # verify

Statistics

bash
# Summary (human-readable)
ccache -s
ccache --show-stats

# Example output:
# Cache directory                     /home/alice/.cache/ccache
# Primary config                      /home/alice/.config/ccache/ccache.conf
# Stats updated                       Wed Jan  1 12:00:00 2025
# Hits:               direct          4821
#        preprocessed          103
#        Total                 4924
# Misses                       1032
# ...
# Cache size (GB)                0.87 / 10.00 (8.7%)

A high direct hit rate (versus preprocessed) means ccache is working in the fastest mode. A hit rate below 50% usually means something is forcing cache misses — check the log file.

Reset statistics

bash
ccache -z    # zero the stats counters (does not delete cached data)

Clear the entire cache

bash
ccache -C    # delete all cached objects — keeps config

Environment variables

Environment variables override the config file and are useful in CI pipelines:

VariablePurposeExample
CCACHE_DIROverride cache directory/mnt/cache/ccache
CCACHE_MAXSIZEOverride max_size10G
CCACHE_DISABLEDisable ccache for this invocation1
CCACHE_RECACHEForce recompilation and update cache1
CCACHE_NODIRECTDisable direct mode, use preprocessor only1
CCACHE_SLOPPINESSComma-separated sloppiness optionsinclude_file_mtime
CCACHE_LOGFILEWrite per-call debug log/tmp/ccache.log
CCACHE_COMPILERCHECKHow to identify the compiler%compiler_hash%
CCACHE_BASEDIRNormalize absolute paths for portability/home/runner/work
bash
# Disable ccache for a single problematic build
CCACHE_DISABLE=1 make -j$(nproc)

# Point to a shared network cache (NFS/SMB)
CCACHE_DIR=/nfs/shared/ccache make -j$(nproc)

# Force a clean recompile but still update the cache
CCACHE_RECACHE=1 make -j$(nproc)

Direct vs preprocessor mode

ccache has two operating modes:

Preprocessor mode (fallback): ccache runs the preprocessor (-E) on the source, hashes the preprocessed output plus flags, then either returns the cached object or compiles and caches. This always produces a correct hash but is slower because it runs the preprocessor twice (once by ccache, once by the real compiler).

Direct mode (default, faster): ccache hashes the original source file plus its #include dependencies without running the preprocessor. It builds a manifest of included files and their checksums. A cache hit requires that all included files are unchanged. Direct mode avoids the double-preprocessor overhead.

bash
# Check which mode was used (in log)
CCACHE_LOGFILE=/tmp/ccache.log make 2>&1
grep "Result:" /tmp/ccache.log | head -20
# Result: direct cache hit
# Result: preprocessed cache hit
# Result: cache miss

Sloppiness options

Sloppiness options tell ccache to ignore certain differences when computing the hash. Useful for header-heavy projects:

ini
# ccache.conf
sloppiness = include_file_mtime
# Ignore file modification times — only hash file content.
# Safe as long as you don't use mtime-sensitive macros like __DATE__.

sloppiness = include_file_ctime
# Ignore ctime changes (e.g., from git checkouts that preserve mtime).

sloppiness = file_stat_matches
# Assume files with same size+mtime have same content — fastest but
# can give stale results if you modify a file without changing its mtime.

sloppiness = pch_defines
# Allow PCH cache hits even if the PCH was built with different -D flags
# (provided the defines don't affect the PCH itself).

sloppiness = time_macros
# Ignore __TIME__ and __DATE__ — lets you cache files that embed build time.
# WARNING: cached objects will contain the *original* build time, not now.

# Common combination for aggressive caching in CI
sloppiness = include_file_mtime,include_file_ctime,file_stat_matches

Docker layer caching with ccache

Without care, Docker builds throw away the ccache between runs, defeating the purpose. The solution is to mount an external cache directory as a volume.

dockerfile
# Dockerfile
FROM ubuntu:24.04 AS builder

RUN apt-get update && apt-get install -y \
    cmake ninja-build clang ccache

WORKDIR /src
COPY . .

# --mount=type=cache persists the ccache across `docker build` invocations
RUN --mount=type=cache,target=/ccache,id=myproject-ccache \
    CCACHE_DIR=/ccache \
    cmake -B build \
      -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
      -DCMAKE_BUILD_TYPE=Release && \
    cmake --build build -j$(nproc)
bash
# Build with BuildKit cache mount
DOCKER_BUILDKIT=1 docker build -t myimage .

# The cache at /ccache persists on the host between builds
# (stored in Docker's internal BuildKit cache, not a named volume)

Named volume approach

If BuildKit is not available, use a named Docker volume:

bash
# Create a persistent volume
docker volume create ccache-vol

# Build with the volume mounted
docker run --rm \
    -v ccache-vol:/root/.cache/ccache \
    -v $(pwd):/src \
    -w /src \
    ubuntu:24.04 \
    bash -c "apt-get install -y ccache cmake && cmake -B build && cmake --build build"

# The volume persists across `docker run` invocations

docker-compose

yaml
# docker-compose.yml
services:
  builder:
    image: ubuntu:24.04
    volumes:
      - ccache:/root/.cache/ccache
      - .:/src
    working_dir: /src
    environment:
      - CCACHE_DIR=/root/.cache/ccache
      - CCACHE_MAXSIZE=5G
    command: bash -c "cmake -B build -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && cmake --build build"

volumes:
  ccache:

CI integration

GitHub Actions

The actions/cache action can persist the ccache directory between runs:

yaml
# .github/workflows/build.yml
name: Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: sudo apt-get install -y cmake ninja-build clang ccache

      - name: Cache ccache
        uses: actions/cache@v4
        with:
          path: ~/.cache/ccache
          # Key changes only when compiler version or branch changes;
          # restore-keys allows a partial hit from the same branch
          key: ccache-${{ runner.os }}-${{ env.CC }}-${{ github.ref_name }}-${{ github.run_id }}
          restore-keys: |
            ccache-${{ runner.os }}-${{ env.CC }}-${{ github.ref_name }}-
            ccache-${{ runner.os }}-${{ env.CC }}-

      - name: Configure ccache
        run: |
          ccache --set-config max_size=500M
          ccache --set-config compression=true
          ccache -z   # reset stats for this run

      - name: Configure
        run: |
          cmake -B build -G Ninja \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_CXX_COMPILER_LAUNCHER=ccache

      - name: Build
        run: cmake --build build -j$(nproc)

      - name: Print ccache stats
        if: always()
        run: ccache -s

GitLab CI

yaml
# .gitlab-ci.yml
variables:
  CCACHE_DIR: "$CI_PROJECT_DIR/.ccache"
  CCACHE_MAXSIZE: "1G"

build:
  image: ubuntu:24.04
  stage: build
  cache:
    key: "ccache-$CI_COMMIT_REF_SLUG"
    paths:
      - .ccache/
  before_script:
    - apt-get install -y cmake ninja-build clang ccache
    - ccache -z
  script:
    - cmake -B build -G Ninja
        -DCMAKE_BUILD_TYPE=Release
        -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
    - cmake --build build -j$(nproc)
    - ccache -s

Common pitfalls

Precompiled headers (PCH)

ccache supports PCH but requires special handling. Using a PCH with the standard workflow often causes cache misses because the PCH binary includes timestamps.

bash
# Tell ccache to ignore PCH mtime and certain defines
ccache --set-config sloppiness=pch_defines,include_file_mtime

# Or disable PCH entirely during cache-enabled builds
cmake -B build -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
               -DCMAKE_CXX_COMPILER_LAUNCHER=ccache

-MF dependency files

Some build systems pass -MF depfile to generate Makefile dependency files alongside compilation. ccache transparently handles this in preprocessor mode but may emit spurious cache misses in direct mode if the dependency file path is embedded in the object. If you see a flood of misses with -MF, try:

bash
CCACHE_NODIRECT=1 make -j$(nproc)

Incompatible compiler flags

Certain flags force a cache miss every time because they embed unique information:

FlagProblem
-DBUILD_TIMESTAMP=$(date)Changes every invocation
-DGIT_COMMIT=$(git rev-parse HEAD)Changes every commit
-frecord-gcc-switchesEmbeds full flag list in object
-ftime-reportTiming output differs per run

Move build metadata into a separate translation unit that you deliberately exclude from caching, rather than injecting it via -D flags into every file.

CCACHE_BASEDIR for relocatable builds

If your CI clones to different absolute paths (e.g., /home/runner/work/proj-1 vs /home/runner/work/proj-2), absolute paths embedded by __FILE__ or -I/abs/path differ and cause misses. Fix with:

bash
export CCACHE_BASEDIR=$PWD
# ccache strips the base dir from hashed paths, making the cache portable

When ccache does not help

  • Generated source files whose content changes every build (version strings, protobuf output with timestamps) — these will always miss.
  • Link time — ccache caches object files only; the link step is not cached. For link speedup use a linker like lld or mold.
  • Always-changing inputs such as files that #include a header with __DATE__ or __TIME__ (unless sloppiness = time_macros is set).
  • Single-file builds — the overhead of hashing exceeds the savings when the translation unit takes under ~0.1 s to compile.
  • LTO (link-time optimization) — LTO object files contain IR, not native code. They can be cached but the linking step (where the real work happens) is not.

For link caching, consider pairing ccache with sccache (which also supports remote caching via S3/GCS) or using thin-LTO which parallelises better.

Edit on GitHubUpdated 2025-01-01T00:00:00.000Z