Developing Ceph Natively on macOS
Problem
I develop Ceph on a Mac with Apple silicon. Can I build Ceph and run a local
vstart.sh test cluster natively, without a Linux VM or container?
Solution
Yes. Clyso has developed a port of Ceph to macOS on Apple silicon, submitted
upstream as a series of pull requests. With the full series applied, the
client libraries, the mon, the OSD, the mgr, the MDS, the RGW and the offline
tools build with the Apple clang from the Xcode Command Line Tools. vstart.sh --memstore brings up all five daemons,
and RADOS, RBD, S3 and CephFS (through ceph-fuse) all work against it.
The main limitation is that BlueStore does not build on macOS, so the OSD
runs on memstore and its data does not survive a restart. macOS is also not
covered by upstream CI, so it is a best-effort development platform, not a
place to run production clusters.
Status of the Upstream Work
At the time of writing (September 2026), Clyso's port is not yet merged into
Ceph main. It was split into seven PRs, each based on main:
| PR | Branch | Content |
|---|---|---|
| #71883 | wip-macos-1-portability | Portability fixes that a stricter toolchain turns up |
| #71884 | wip-macos-2-inttypes | Fixed-width types where the encoding depends on them |
| #71885 | wip-macos-3-builddeps | Build dependencies that are not present on every platform |
| #71886 | wip-macos-4-cephfs-proxy | Build libcephfs_proxy on Linux only |
| #71887 | wip-macos-5-cmake-apple | cmake: recognise AppleClang and arm64 |
| #71888 | wip-macos-6-darwin-compat | The Darwin side of the compatibility layer |
| #71889 | wip-macos-7-macos | macFUSE, ARM CPU feature probing, do_cmake_macos.sh and doc/dev/macos.rst |
All seven are needed for a working build. The original combined PR,
#71232 (branch wip-macos-build),
is kept as a draft for reference but predates the review changes in the split
series.
Getting the Code
Until the series is merged, fetch the seven branches from
github.com/dvanders/ceph and merge them together:
$ git clone https://github.com/ceph/ceph.git && cd ceph
$ git remote add dvanders https://github.com/dvanders/ceph.git
$ git fetch dvanders
$ git checkout -b macos dvanders/wip-macos-1-portability
$ for n in 2-inttypes 3-builddeps 4-cephfs-proxy 5-cmake-apple 6-darwin-compat 7-macos; do
git merge --no-edit dvanders/wip-macos-$n
done
Merge them as above rather than onto the tip of main: main has moved on
since the branches were cut, and at the time of writing #71885 needs a rebase
and conflicts with it. Once the PRs are merged, a plain checkout of main
is enough.
Installing Dependencies
Install the build dependencies with Homebrew:
$ brew install cmake ninja boost ccache openssl@3 snappy lz4 zstd nss \
icu4c sqlite libevent utf8proc c-ares
The build needs Cython and PyYAML even if the Python bindings are not wanted. Homebrew's Python is externally managed, so install them into a virtualenv in the source tree:
$ uv venv --python 3.12 .venv
$ uv pip install --python .venv/bin/python cython pyyaml setuptools \
prettytable python-dateutil
Initialise only the submodules that are needed, and do not pass
--depth 1. Several submodules pin a commit that is not the tip of their
default branch, and a shallow clone silently leaves them on master
(git submodule status then shows a leading +):
$ git submodule update --init src/fmt src/BLAKE3 src/xxHash src/rocksdb \
src/zstd src/googletest src/utf8proc \
src/erasure-code/jerasure/jerasure src/erasure-code/jerasure/gf-complete
Building
do_cmake_macos.sh configures a base build of the mon, the OSD, the client
libraries and the tools. It points cmake at the keg-only Homebrew packages,
uses the virtualenv's Python, and turns off what cannot build on macOS:
$ ./do_cmake_macos.sh
$ ninja -C build vstart-base librbd rbd
The mgr, MDS, RGW and ceph-fuse need extra packages and options. Build them
in a separate directory:
$ brew install oath-toolkit
$ brew install --cask macfuse
$ git submodule update --init src/libkmip
$ git submodule update --init --recursive src/s3select
$ BUILD_DIR=build-all ./do_cmake_macos.sh \
-DWITH_SYSTEM_BOOST=OFF \
-DWITH_MGR=ON -DWITH_CEPHFS=ON -DWITH_RADOSGW=ON -DWITH_FUSE=ON \
-DWITH_MGR_ROOK_CLIENT=OFF -DWITH_MGR_DASHBOARD_FRONTEND=OFF \
-DWITH_RADOSGW_AMQP_ENDPOINT=OFF -DWITH_RADOSGW_KAFKA_ENDPOINT=OFF \
-DWITH_RADOSGW_LUA_PACKAGES=OFF -DWITH_RADOSGW_D4N=OFF \
-DWITH_RADOSGW_POSIX=OFF -DWITH_RADOSGW_DBSTORE=OFF \
-DWITH_RADOSGW_SELECT_PARQUET=OFF
$ ninja -C build-all vstart-base ceph-mgr ceph-mds radosgw radosgw-admin \
ceph-fuse cython_cephfs cython_rbd
-DWITH_SYSTEM_BOOST=OFF makes Ceph build its own Boost, so that
Boost.Python matches the virtualenv's interpreter. The cython_cephfs and
cython_rbd targets must be named explicitly: ninja all does not build
them, and without cython_cephfs the volumes mgr module cannot load, which
makes vstart.sh retry ceph fs volume create forever.
Running a vstart Cluster
$ cd build-all
$ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
$ export PYTHONPATH=$PWD/../.venv/lib/python3.12/site-packages
$ MON=1 OSD=1 MDS=1 MGR=1 RGW=1 ../src/vstart.sh -n --memstore -X
$ export CEPH_CONF=$PWD/ceph.conf
$ ./bin/ceph -s
services:
mon: 1 daemons, quorum a
mgr: x(active)
mds: 1/1 daemons up
osd: 1 osds: 1 up, 1 in
rgw: 1 daemon active (1 hosts, 1 zones)
For RADOS and RBD work alone, MON=1 OSD=1 MDS=0 MGR=0 RGW=0 against the base
build directory is enough and needs neither environment variable.
To mount CephFS, approve the macFUSE kernel extension in System Settings and restart once, then:
$ mkdir -p /tmp/cephfs
$ ./bin/ceph-fuse -c ceph.conf --client_fs a /tmp/cephfs
$ umount /tmp/cephfs
Discussion
Things That Look Like Bugs But Are Not
- radosgw aborts at startup with only
libobjcandCoreFoundationframes in the backtrace. radosgw forks without exec when it daemonises, and touching the Objective-C runtime afterfork()is fatal on macOS. SetOBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES. - Writes hang after a few seconds of benchmarking.
memstore_device_bytesdefaults to 1 GiB, and memstore is fast enough to fill it in seconds. The OSD then marks itself full and blocks writes. Bound the test (rados bench 5 write --max-objects 16) or raisememstore_device_bytes. ceph pg statand other commands hang without a mgr. Commands implemented by mgr modules hang instead of failing when no mgr is running, andceph -sshows an emptydatasection. Useceph health detailor the daemons' admin sockets instead.- mgr modules fail to load. The embedded interpreter only sees the
virtualenv through
PYTHONPATH. Install the missing pip packages (e.g.requests cherrypy scipy urllib3 cryptography) into.venv. The dashboard additionally needs-DWITH_MGR_DASHBOARD_FRONTEND=ONand npm. - The cluster is in
HEALTH_ERRbecause ofdevicehealth. This module reports its failures as errors, and macOS cannot answer its SMART queries.
Hardware Acceleration
The ARMv8 accelerated paths are used automatically on Apple silicon: hardware crc32c (about 7400 MB/s against 1260 MB/s for the portable implementation), NEON gf-complete for jerasure and shec, the ISA-L erasure code plugin, and ISA-L igzip for the zlib compressor. This makes a Mac a reasonable platform for developing and testing erasure code and checksum changes on ARM.
Limitations
- BlueStore. Darwin's POSIX aio does not implement kqueue completion
delivery (
SIGEV_KEVENT) and allows only 16 outstanding requests per process, so BlueStore needs a new IO backend rather than the FreeBSD one. Develop and test BlueStore changes on Linux. - Tests. With
-DWITH_TESTS=ON, all but seven of the test targets build. The upstream document lists which. Building is not the same as passing: the test suite has not been run on macOS as a whole. - No
/proc, no per-process POSIX timers, no NUMA or CPU affinity. The code paths that depend on them return errors, and theraiseadmin socket command is unavailable.
For the complete and current guide, see doc/dev/macos.rst in the source
tree once the series is merged, or on the
wip-macos-7-macos branch
until then.