Fixing Docker's Slow Performance on MacOS

Docker is designed for Linux. It works most efficiently on Linux systems due to its close integration with the Linux kernel.

When handling large filesystems, like the ones built with PHP and  Node, Docker desktop (MacOS Environment) experiences significant lag. The main reason is how file synchronization is implemented in Docker for Mac. Plus, disk space consuming behavior of such big PHP Projects.

In this article, we will discuss the reasons behind the slowness of Docker containers in Mac Environment and what role bind mounts and volumes play in mitigating these to a certain level. Then we will look at what practices to be adopted in general to deal with all such occurring bottlenecks.

It is imperative to say that docker was built for Linux and code-heavy developer environments and making it to work as efficiently on a desktop may not be a very viable idea. Let’s read on!

Table Of Contents:-

Issue of Docker Slowness on macOS

Docker containers rely heavily on file system operations to read, write, and share files. macOS uses a different file system (HFS+) compared to the typical file systems used in Linux (such as ext4). This difference in file system behavior can lead to slower I/O operations and increased latency when interacting with containers.

Docker for macOS uses a lightweight virtualization approach through a hypervisor (HyperKit or VirtualBox) to create a Linux virtual machine (VM) on which Docker containers run. This adds an extra layer of abstraction and can introduce overhead, causing some performance degradation compared to running Docker directly on a native Linux environment. This can also lead to degradation in networking performance.

Mac computers are not typically optimized for the kind of resource-intensive workloads that Docker containers might demand. This includes limitations in terms of CPU, memory, and other hardware resources.

Docker Desktop is a user-friendly application that simplifies running Docker on macOS, but it might introduce some overhead due to its graphical interface and additional features. Using the Docker CLI directly might offer more control and potentially better performance.

Example

We'll use a Node.js application that processes a large dataset by reading input files, performing some computation, and writing output files. We'll compare the performance on macOS (native and with bind mounts) and on Linux (native).

Let's start with the Dockerfile and the Node.js application code:

Dockerfile:

FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "app.js"]

app.js (Node.js Application):

const fs = require("fs");
const path = require("path");
const inputDirectory = "/app/input"; // Input directory
const outputDirectory = "/app/output"; // Output directory
const files = fs.readdirSync(inputDirectory);
files.forEach((file) => {
  const inputFile = path.join(inputDirectory, file);
  const outputFile = path.join(outputDirectory, file);
  const data = fs.readFileSync(inputFile, "utf-8");
  const processedData = processData(data); // Some imaginary processing function
  fs.writeFileSync(outputFile, processedData, "utf-8");
});
console.log("Processing complete.");

Assuming you have a directory structure like this:

project/
├── Dockerfile
├── app.js
├── input/
│   ├── file1.txt
│   ├── file2.txt
│   └── ...
└── output/

You can measure the execution time for each scenario. On Linux, using time before each Docker command will give you the total execution time. And the scenarios being:

  • Linux (Native)
  • Linux (with binds and mounts)
  • Mac (Native)
  • Mac ( with binds and mounts)

You would observe that Docker on macOS (using bind mounts or volumes) would perform better than in the native condition due to the optimizations provided by bind mounts and volumes.

However, it's important to note that even with these optimizations, the performance might still be slightly slower compared to running the same application on a native Linux environment.

The improvements brought by bind mounts and volumes mitigate some of the file system-related slowness on macOS, but they don't completely eliminate the performance gap between macOS and Linux.

Common Bottlenecks in Docker Performance

1. Disk I/O Limitations on MacOS

Disk I/O (Input/Output) refers to the process of reading from and writing to storage devices like hard drives or SSDs. In the context of Docker, disk I/O is critical because containers rely on file operations for various tasks such as reading configurations, writing logs, and exchanging data.

Issue: On macOS, Docker's performance is affected by the underlying file system, which is HFS+ (Hierarchical File System Plus). HFS+ is optimized for macOS but wasn't designed with the same considerations as Linux file systems (like ext4) that Docker is optimized for. This difference in file system design leads to slower I/O operations when Docker containers interact with the file system.

What happens then?

This can cause noticeable delays when containers are reading and writing files. Slow disk I/O can be particularly noticeable when building Docker images, copying large files, or when containers with high I/O demands are running.

Solution: While you can't change the macOS file system, you can take several steps to mitigate this issue:

  • Optimize Dockerfile: Organize your Dockerfile to minimize the number of layers and reduce unnecessary file operations.
  • Use Cached Volumes: Utilize Docker's volume caching mechanisms to reduce redundant file system operations.
  • Leverage Bind Mounts: When sharing data between the host and containers, use bind mounts efficiently to minimize copying of files.

2. Networking Overhead and Latency

Networking is crucial for communication between Docker containers, with the host system, and potentially with external services. Efficient networking is essential for inter-container communication and proper functioning of applications.

On macOS, Docker's networking involves additional layers due to the virtualization approach used (HyperKit or VirtualBox). This extra layer introduces network overhead and latency compared to Docker's native environment on Linux.

What happens then?

Docker containers on macOS might experience increased network latency and slightly reduced throughput compared to containers on Linux. This can affect the performance of applications that rely heavily on low-latency networking.

Solution: To improve networking performance on macOS:

  • Use Host Network: When security is not a concern, containers can share the host's network stack directly, reducing network overhead.
  • Optimize Network Settings: Fine-tune network settings like bridge networks to ensure efficient communication between containers while minimizing unnecessary traffic.

3. Resource Constraints on a Mac Machine

Docker containers require system resources such as CPU, memory, and disk space to run efficiently. The performance of containers can be significantly impacted if these resources are limited.

Mac machines, especially laptops, are typically designed for general-purpose use and might not offer the same level of resources as dedicated servers. Running resource-intensive containers can lead to bottlenecks.

What happens then?

Containers with high CPU or memory demands might struggle to perform optimally on a Mac machine. This can result in slow application response times, increased latency, and even crashes if resource limits are consistently exceeded.

Solution: To manage resource constraints on macOS:

  • Adjust Container Limits: Set resource limits for CPU and memory when running containers to ensure fair allocation and prevent resource-hogging.
  • Use Efficient Images: Choose lightweight base images and minimize unnecessary software installations within containers to reduce memory usage.
  • Consider Cloud or Remote Servers: For resource-intensive workloads, consider offloading container execution to cloud-based environments or remote servers with more resources.

Addressing these common bottlenecks requires a combination of optimizing Docker configurations, leveraging appropriate Docker features, and considering the constraints of the macOS environment. So next up we are looking at ways to fix these problems.

How bind mounts and volumes can help?

Bind mounts and volumes are two mechanisms that can help address Docker slowness on macOS by improving file system performance and data sharing between the host and containers.

These mechanisms optimize the way Docker interacts with the host's file system, mitigating some of the performance challenges caused by the differences between macOS and Linux file systems.

1. Bind Mounts

Bind mounts allow you to directly map a directory or file from the host system into a container. This enables the container to access and manipulate the host's file system directly. Bind mounts can help improve Docker's performance on macOS by reducing the overhead of file system translations and copying.

Benefits:

  • Direct Access: Containers can read and write files directly on the host's file system, reducing the translation overhead.
  • Improved I/O: Bind mounts eliminate the need to copy files into the container's file system, improving read and write operations.

Considerations:

  • While bind mounts offer better performance, they might reduce isolation between the host and the container. Changes made by the container can affect the host's files.

2. Volumes

Volumes are a Docker feature that provides a more controlled way of sharing and persisting data between containers and the host. Docker volumes are stored in a location managed by Docker, providing isolation and better performance compared to bind mounts.

Benefits:

  • Performance Isolation: Docker volumes have their own file system and are managed by Docker, isolating them from the host's file system.
  • Caching: Docker volumes use a caching mechanism that can improve read and write operations.
  • Persistence: Data stored in Docker volumes persists even when containers are stopped or removed.

Considerations:

  • Volumes might require additional management compared to bind mounts.
  • For better performance, choose named volumes, which have caching and performance optimizations.

Both bind mounts and volumes can help alleviate Docker slowness on macOS by optimizing file system interactions. When using these mechanisms, consider the following best practices:

  • Use Named Volumes: If possible, choose named volumes over anonymous volumes for improved performance and management.
  • Mount Specific Directories: Only bind mount or use volumes for the specific directories or files your application requires, reducing unnecessary interactions with the file system.
  • Choose the Right Strategy: Consider the trade-offs between performance and isolation when deciding between bind mounts and volumes.

By leveraging bind mounts and volumes effectively, you can mitigate some of the file system-related performance challenges that Docker faces on macOS, ultimately improving container performance and responsiveness.

How to rectify and optimize the bottlenecks in general?

1. Analyze Your Docker Setup

  • Monitor Container Performance: Use tools like docker stats to get real-time metrics of your container's CPU, memory, and network usage.
  • Docker Inspect: Use the docker inspect command to get detailed information about your container configuration and identify potential misconfigurations.
  • Review Dockerfile: Ensure you're following best practices. Minimize the number of layers, avoid large files, and reduce unnecessary installations or operations. Check for Outdated Images - Outdated or heavyweight images can slow down container startup. Regularly clean up unused images.
  • Check Volume Bindings: Excessive or incorrect volume bindings can slow down container performance. Ensure only necessary directories are bound.

2. Docker Desktop vs Docker CLI - Pros and Cons

Choosing between Docker Desktop and the Docker CLI depends on your priorities and workflow:

i.) Docker Desktop

Pros:

  • User-friendly GUI for managing containers.
  • Integration with macOS notifications and system tray.
  • Simplified network setup and volume sharing.

Cons:

  • Slightly more overhead due to its GUI nature.
  • May contain additional features that an advanced user might not need.

ii.) Docker CLI

Pros:

  • Direct control over all Docker operations.
  • Potentially faster as there’s no GUI overhead.
  • Can be used in automation scripts.

Cons:

  • Requires knowledge of command-line operations.
  • No GUI might make certain tasks more complex for some users.

Evaluate your preference for control and efficiency against the convenience of a graphical interface when deciding which option to use.

3. Leveraging Docker Caching Mechanism

  • Layer Caching: Docker builds images layer by layer. Ensure common operations are at the top of your Dockerfile so they're cached for subsequent builds. Utilize multi-stage builds to create lightweight final images. Intermediate build stages can be optimized for build speed without adding unnecessary baggage to the final image.
  • .dockerignore File: Exclude files and directories that are not necessary for your container. This reduces build context size and speeds up image creation.
  • Cache Dependencies: When building, consider copying dependencies first before copying source code. This way, changes to your source code won't invalidate the cached layers containing dependencies.

4. Network Enhancements

  • Custom Bridge Networks: Instead of using the default bridge network, create custom bridge networks for better isolation and DNS resolution. Choose appropriate network drivers for your use case. If you're facing network latency issues, consider using the "host" network mode for containers that require high-speed network access.
  • Minimize Exposed Ports: Only expose necessary ports in your Docker containers to reduce potential network overhead.
  • Caching External Resources: If your containers rely on external resources, consider caching them locally to reduce network requests.

5. Balancing CPU and Memory Usage

  • Set Resource Limits: Use flags like --cpus, --cpu-shares, and --memory when running containers to allocate specific resources.
  • Swarm Mode: If running multiple containers, Docker Swarm can help in distributing loads and managing resources more efficiently.
  • Monitor and Adjust: Regularly check the resource utilization and adjust allocations based on container needs.  Monitor container resource usage and adjust limits accordingly. Utilize tools like docker stats and docker top.

6. Third-Party Tools to the Rescue

  • Docker-sync: Improves performance of volume mounts by syncing files instead of direct mounting, which is beneficial for macOS's file system.
  • Skaffold: Helps in automating the workflow for building, pushing, and deploying Docker containers.
  • ctop: Provides a concise and condensed overview of real-time metrics for multiple containers.
  • LocalStack: For developers using AWS services in their applications, LocalStack can mock these services locally, reducing the need for constant data transfer.

Implementing these tips and strategies can drastically improve Docker's performance on macOS. However, always remember to monitor your setup and adjust configurations based on the specific needs of your projects.

Conclusion

Remember that Docker performance optimization is a continuous process. Regularly monitor and assess your changes to ensure they're having the desired impact and adapt them as your application evolves.

Each optimization might have varying levels of impact depending on the nature of your workload, so experimentation and iteration are key to achieving the best results.

It's important to note that while these optimizations can improve Docker's performance on macOS, they might not completely eliminate the performance gap compared to running Docker on a native Linux environment.

The choice of whether to optimize Docker on macOS or explore other options (such as using cloud-based environments) should be based on the specific needs and constraints of the development workflow.


Atatus Container Monitoring

Container Monitoring with Atatus lets you track and analyze the performance, health, and resource utilization of containerized applications. It ensures that applications run optimally, helps detect and address issues early, and assists in resource allocation for efficient scaling.

Docker Logs Monitoring
Docker Container Monitoring

The metrics include CPU utilization, memory usage, disk I/O, network traffic, container uptime, response times, and error rates. These metrics provide insights into application performance and resource consumption.

Container monitoring enables proactive issue detection, reduces downtime, optimizes resource allocation, aids in capacity planning, and provides insights into application behavior for continuous improvement.

Try your 14-day free trial of Atatus.

Aiswarya S

Aiswarya S

Writes technical articles at Atatus.

Monitor your entire software stack

Gain end-to-end visibility of every business transaction and see how each layer of your software stack affects your customer experience.