Docker filesystem performance on OSX
How to work around poor filesystem performance with binds.
Development activities can involve lots of filesystem activity. Timing of NodeJS dev activities inside a Docker container on an OS X host is quite sensitive to the way we present the node_modules
directory to the container. This is just a brief summary of the extensive literature (see References).
Summary
Mask high churn directories inside your source tree (e.g. node_modules
) with a Docker volume.
Options
Bind mount
The default starting point is to bind mount the entire source directory to the container, and do work inside the container on it. By default the host's and the container's view of the directory will be entirely consistent.
services:
app:
working_dir: /app
volumes:
- type: bind
source: '.'
target: /app
Bind mount with delegated consistency
The next thing to try is to use the OS X consistency configuration to allow the container to have the authoritative view of the directory and allow Docker to update the host later:
services:
app:
working_dir: /app
volumes:
# Bind source to container
- type: bind
source: '.'
target: /app
consistency: delegated
Bind mount with volume masking node_modules
Another option is to allow most filesystem activity to happen in a Docker volume; bind the source directory to the container as before but then mask the node_modules
with a Docker volume.
services:
app:
working_dir: /app
volumes:
- type: bind
source: '.'
target: /app
- type: volume
source: node_modules
target: /app/node_modules
volume:
nocopy: true
Results
The following timing is on a moderately sized ReactJS project (about 14K LOC javascript) and shows that the volume masking option is a clear winner, getting close to local performance:
Type | npm i |
npm t |
---|---|---|
Bind | 3m39s | 1m11s |
Bind (delegated) | 3m51s | 1m21s |
Volume | 0m38s | 0m13s |
(Local) | 0m31s | 0m11s |
(The delegated consistency performance wasn't any better than the default, which is surprising.)