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.
# 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 fastccache 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
# 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 buildVerify:
ccache --version
# ccache version 4.x.y
which ccache # /usr/bin/ccache or /usr/local/bin/ccacheBasic usage
Environment variables
The simplest way — set CC and CXX before invoking your build system:
export CC="ccache gcc"
export CXX="ccache g++"
# Or with Clang
export CC="ccache clang"
export CXX="ccache clang++"
make -j$(nproc)Symlink masquerade
On some systems ccache installs compiler-named symlinks so you don't have to change CC/CXX:
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 ccacheCMake integration
The modern CMake approach uses launcher variables. Set them in CMakePresets.json or pass them on the command line:
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
# 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
{
"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:
# ~/.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_definesChanges take effect immediately — no restart needed.
Setting config via the command line
ccache --set-config max_size=20G
ccache --set-config compression=true
ccache --get-config max_size # verifyStatistics
# 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
ccache -z # zero the stats counters (does not delete cached data)Clear the entire cache
ccache -C # delete all cached objects — keeps configEnvironment variables
Environment variables override the config file and are useful in CI pipelines:
| Variable | Purpose | Example |
|---|---|---|
CCACHE_DIR | Override cache directory | /mnt/cache/ccache |
CCACHE_MAXSIZE | Override max_size | 10G |
CCACHE_DISABLE | Disable ccache for this invocation | 1 |
CCACHE_RECACHE | Force recompilation and update cache | 1 |
CCACHE_NODIRECT | Disable direct mode, use preprocessor only | 1 |
CCACHE_SLOPPINESS | Comma-separated sloppiness options | include_file_mtime |
CCACHE_LOGFILE | Write per-call debug log | /tmp/ccache.log |
CCACHE_COMPILERCHECK | How to identify the compiler | %compiler_hash% |
CCACHE_BASEDIR | Normalize absolute paths for portability | /home/runner/work |
# 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.
# 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 missSloppiness options
Sloppiness options tell ccache to ignore certain differences when computing the hash. Useful for header-heavy projects:
# 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_matchesDocker 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.
BuildKit cache mount (recommended)
# 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)# 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:
# 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` invocationsdocker-compose
# 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:
# .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 -sGitLab CI
# .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 -sCommon 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.
# 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:
CCACHE_NODIRECT=1 make -j$(nproc)Incompatible compiler flags
Certain flags force a cache miss every time because they embed unique information:
| Flag | Problem |
|---|---|
-DBUILD_TIMESTAMP=$(date) | Changes every invocation |
-DGIT_COMMIT=$(git rev-parse HEAD) | Changes every commit |
-frecord-gcc-switches | Embeds full flag list in object |
-ftime-report | Timing 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:
export CCACHE_BASEDIR=$PWD
# ccache strips the base dir from hashed paths, making the cache portableWhen 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
lldormold. - Always-changing inputs such as files that
#includea header with__DATE__or__TIME__(unlesssloppiness = time_macrosis 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.