Docker Desktop for Macの実用的な代替手段: lima + Docker

## 2023/07/04追記: 現在はRacher Desktopがおすすめ

Rancher Desktopの登場により、この記事でやろうとしている環境構築を簡単に実現できるようになっています。設定でコンテナランタイムを`dockerd (moby)`に設定すれば、Dockerを利用することができます。

https://qiita.com/moritalous/items/14d4099023981dcf4fd2

## はじめに

昨年のPull Limitに引き続き、Docker社が再び動きました。

https://www.itmedia.co.jp/news/articles/2109/02/news112.html

今後Docker Desktopを使用する場合、中規模以上の企業では有料プランを使用する必要があります。ただし有料プランで提供されるサービスは開発者にとってはあまり価値がないものですし、この有料プランを全社的に導入するという話を進めるのも中々骨が折れるでしょう。

> NOTE: Docker Desktopの有料プラン移行には2022年1月31日までの猶予期間があります

既に一部ではDocker Desktopの代替手段を求める動きがあり、例えば以下のようなnerdctl, podmanによる代替手段を紹介している記事がいくつか挙がっています。

- [containerd & Lima: Open source alternative to Docker for Mac](https://medium.com/nttlabs/containerd-and-lima-39e0b64d2a59)
- [Docker Desktopの代替方法 - Windows and Mac編](https://zenn.dev/koduki/articles/ba54daaba28f93)

しかしこれらのアプローチはDocker CLIやDocker REST API(Docker Engine API)の制限が色々とあるため、後述する私のDocker利用用途では多くの問題が発生します。
特にDockerのBuildkitを活用(ex. docker buildの`--ssh`, `--secret`オプション)したり、Docker CLIやDocker REST API(Docker Engine API)の操作を行っているようなツールやライブラリ等(ex. kind, Testcontainers)を使用している場合は実用性の観点では極めて厳しいというのが正直な感想です。

このため**本記事ではDocker CLIとDocker REST API (Docker Engine API)を利用できるアプローチであるlima + Dockerによる実用的な代替手段について紹介**します。


## 主な用途

私はローカル環境上の開発で以下のような用途でDocker Desktopを使用しています。このためこれらのユースケースを一通りカバーできる必要があります。

- `docker build`コマンドでDockerイメージのビルドを行う。
    - ビルドの際にはBuildKitを利用して、`--ssh`や`--secret`等のオプションも活用するプロジェクトもある。
- `docker run`コマンドでDockerコンテナの動作検証などを行う。
    - 必要に応じて`-p`オプションを指定して`localhost:8080`にアクセスすることもある。
- KubernetesのController, Operatorの開発や検証を行うため、`kind`コマンドでKinDクラスタを立ち上げる。
    - クラスタにPodをデプロイし、`kubectl port-forward`コマンドでPod等のエンドポイントにアクセスすることもある。

基本的に`docker build`や`docker run`等のDocker CLI + Docker Engine Serverの部分のみを使用しているため、Docker Desktopが提供しているUI等は一切使用していません。
そういったUIが必要な人はVisual Studio CodeのDockerプラグインあたりを試すことをお勧めします。


## lima + Dockerの基本構成

本記事で紹介するlima + Dockerのアプローチは、macOSホストとLinux VMで以下のような構成をとります。

- macOSにDocker CLIを配置し、`docker`コマンドを実行可能にする。
- Linux VM上にDocker Engine Server (Docker Daemonとも呼ぶ)を配置する。
- macOSのDocker CLIがlocalhost経由のTCP通信でLinux VM上のDocker Engine Serverにアクセスできるように設定する。

これによりmacOSホストから`docker`コマンドを実行すると、そのリクエストおよびDocker Context(COPY等に使えるファイル等)がLinux VM上のDocker Engine Serverに転送され、Docker Engine Serverで`docker build`, `docker run`等の処理が動きます。

構成図にするようと次のようになります。

![lima-docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174502/47488d8a-ea59-5029-2c65-935a07b899ca.png)


> **NOTE: 何故Linux VMが必要なのか？**
> Docker CLIはLinux以外でも動作しますが、Docker Engine Serverについては基本的にLinux上でしか動作しません。(Windowsコンテナは事情が違いますが、話がややこしくなるのでここでは除外します)
> Docker DesktopではDocker Engine ServerをWindowsやmacOSで動かしているように見えます、これは裏でLinux VMを立ち上げてVM上でDocker Engine Serverを起動することにより実現しています。


## limaとは？

limaはmacOS上でLinux VMの作成＆起動を実現するOSSプロジェクトです。

VirtualBoxと比較すると以下のような利点があるため、今回はこちらを使用します。

- limaのツールの導入が簡単。(`brew install lima`)
- Linux VMのセットアップが簡単。(`limactl start`コマンドだけでUbuntuログイン可能な状態まで)
- 標準でmacOSホストのファイルシステム、ネットワーク(localhost)がLinux VMと共有されているため、`Host <-> Guest`のシームレスな連携が可能。

> NOTE: 一応もう一つの利点として「containerd + nerdctlが同梱しているため、初めからコンテナのイメージ作成や稼働ができる」というのもありますが、前述した通り今回はそもそもそれを使わずDockerを使用します。

ここではlimaに関する説明は割愛するため、興味がある人は公式レポジトリのExampleなどを確認してください。

https://github.com/lima-vm/lima#examples


> NOTE: limaについては細かい説明はしませんが、`lima <コマンド文字列>`を実行すると「lima VM上でコマンド文字列の内容を実行する」ことになります。このため`lima <コマンド文字列>`の部分はlima VM上で作業していることになります。


## セットアップ手順

### macOSホストへのDocker CLI導入

まずはmacOSホスト側にDocker CLIを導入します。

Docker Desktopを入れないとmacOSにDocker CLIを導入できないと思っている人もいるようですが、実は以下のURLからダウンロードできるファイルに含まれてます。Docker Engineも現時点の最新版を導入することになるため、Docker CLIも最新版をダウンロードします。

https://docs.docker.com/engine/install/binaries/#install-client-binaries-on-macos

- [Intel Mac binary](https://download.docker.com/mac/static/stable/x86_64/)
- [M1 Mac binary](https://download.docker.com/mac/static/stable/aarch64/)

ダウンロードが完了したらtgzファイルと解凍して`docker/docker`のバイナリをPATHが通っているディレクトリに配置してください。

以下に`$HOME/bin`にIntel Mac Binaryをインストールする例を示します。

```bash
# Download binary
curl -OL https://download.docker.com/mac/static/stable/x86_64/docker-20.10.8.tgz

# Install Docker CLI
mkdir -p $HOME/bin
tar xzvf docker-20.10.8.tgz
mv docker/docker $HOME/bin/.
chmod 755 $HOME/bin/docker

# Check appropriate docker binary is used
which docker

# Check CLI works
docker version
```

docker versionコマンドでClientの情報が表示されればOKです。(Serverの情報はエラーで表示されません)

最後にlima VM上のDocker EngineとTCP通信できるように、macOSホスト側にDockerの環境変数を設定します。ここでは$HOME/.profileにDOCKER_HOSTの設定を追加しています。

> NOTE: 以降でセットアップするlima VM上のDocker Engine Serverでは、macOS上からlocalhost(127.0.0.1)経由でアクセスできるようにDocker Daemonの設定を行います。

```bash
echo  "export DOCKER_HOST='tcp://127.0.0.1:2375'" >> $HOME/.profile
source $HOME/.profile
```


### lima VM + Docker Engine Serverのセットアップ

> NOTE: lima VMとはlimaで作成したLinux VMのことを指す

#### lima VMの準備

まずはlimaでLinux VMを作成＆起動し、lima VM上にDocker Engineをセットアップします。

Homebrewでlimaをインストールし、lima VMを立ち上げます。

```bash
# Install lima
brew install lima

# Create & start lima VM
# NOTE: Select 'Open an editor to override the configuration' to change resource setting if necessary.
limactl start

# Running command in VM with `lima`
lima uname -r
```

> NOTE: ちなみにデフォルト設定だとVMに確保されるリソースはやや多め(ex. CPU 4コア、メモリ4GB)となっているため、設定を変更したい場合は`limactl start`を実行した時に`Open an editor to override the configuration`を選択してYAMLコンフィグを修正してください。([Link](https://github.com/lima-vm/lima#configuration))

これでlima VMの作成と起動が完了しました。

なおlima VMはmacOS起動時に立ち上がらないので、自動起動が必要な場合は必要に応じて設定を行う必要があります。

#### lima VM上のDocker Engine Serverインストール

次にlima VMにDocker Engine Serverをインストールします。

```bash
# Install Docker on lima VM
curl -fsSL https://get.docker.com | lima

# Setup docker user to run without sudo (Need to restart lima VM; restarting dockerd won't work)
lima sudo gpasswd -a $USER docker
limactl stop; sleep 5; limactl start

# Check docker command works on macOS Host
lima docker version
```

lima docker versionコマンドの結果でClientとServerの情報が両方ともエラー無く表示されていればOKです。

#### macOSのDocker CLIと通信するための設定

最後にmacOSホスト側のDocker CLIからDocker Engine ServerのAPIにアクセスできるようにTCP通信を受け付けられるように設定します。

lima VM上に`/etc/docker/daemon.json`をエディタで開いて作成します。ここではvimを使った例を示します。

```
lima sudo vim /etc/docker/daemon.json
```

上記のJSONファイル内容を以下のよう記述して保存します。(TCP通信とUnixソケットの両方を受け付けるように設定)

```json:/etc/docker/daemon.json
{"hosts": ["tcp://127.0.0.1:2375", "unix:///var/run/docker.sock"]}
```

上記の設定はデフォルトのDocker Daemonの引数と衝突するため、これを回避するためにsystemdの上書き設定用のファイルを作成します。ここでもvimを使った例を示します。

```bash
lima sudo mkdir -p /etc/systemd/system/docker.service.d/
lima sudo vim /etc/systemd/system/docker.service.d/override.conf
```

上記のoverride.confのファイル内容を以下のように記述して保存します。

```systemd:/etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
```

最後に設定を反映します。

```
lima sudo systemctl daemon-reload
lima sudo systemctl restart docker.service
```

これでlima VMのDocker Engine Serverの設定も完了です。

### 動作確認

これでmacOSホスト側およびlima VMのセットアップが完了しました。

最後にmacOSホスト上で以下のコマンドを実行し、ClientとServerの情報が両方とも正常に表示されることを確認します。

```
docker version
```

正常に動作することを確認できれば、初めに紹介した「主な用途」にあるようなユースケースでは問題なく利用できます。
またDocker Engine APIにもアクセスできるようになっているため、こちらは未確認ですがJavaのTestcontainersライブラリ等もmacOSホスト上から利用できると思います。

もちろん全てのdockerコマンドやオプションについて動作確認をしているわけではないため、もしdockerコマンドやオプション等で正常に動作しないケースなどがあればコメントで情報を共有いただければと思います。


## おまけ

### docker-composeを使いたい人へ

私はdocker-composeを使用していないため、セットアップ手順にdocker-composeのインストール手順は含めませんでした。

しかしdocker-composeのバイナリはGitHubレポジトリのReleaseページからダウンロードできるようになっているため、こちらから適切なMac BinaryをダウンロードしてPATHが通っているディレクトリに配置すれば利用できるようになります。
(簡単なサンプルを用いた`docker-compose up`は正常動作することを確認済み)

https://github.com/docker/compose/releases/

### 何故podmanやnerdctlでは駄目なのか？

これを機にCLIをpodmanやnerdctlに完全に置き換えようとする人もいると思いますが、**podmanやnerdctlはDocker CLI、Docker REST API (Docker Engine API)、BuildKitなどの互換性や対応は完璧ではありません**。

このためDockerのBuildkit等を活用していたり、Docker REST API(Docker Engine API)を活用しているTestcontainersのようなライブラリを使用している場合はpodman, nerdctlに置き換えるのは不可能ではないかもしれませんが非常に厳しいです。

例えばDocker CLIやBuildKitの互換性の問題の例としては以下のようなものが挙げられます。

- podmanではBuildKitが使えないため、docker buildにおける`--ssh`などのオプションが使えない。
    - Issueなどを見る限りは対応も進んでいるように見るが、対応時期や互換性がどこまであるかが不明。
- nerdctlはdocker psにおける--filterなどのオプションが使えない。kind CLIではこういったオプションも使用しているため、正常にKinDクラスタの操作ができない問題がある。

将来的にはDockerに依存しないエコシステムになると思いますが、現在は過渡期のためDockerにある程度は頼らざるを得ません。



### Virtual Boxでは駄目なのか？

Virtual BoxでLinux VMを作成し、そのVM上で全ての作業を行うのであればVirtual Boxでも良いと思います。しかしその場合はエディタやIDE等のGUIも含めてLinux VM上で動かすためパフォーマンスやバッテリー面で不満があるので個人的にはNGです。

別のやり方として「コーディング等の作業をmacOS上、docker build等をLinux VM上で行う」といった役割分担をすることも可能です。しかしこの場合はホストとVMの間でファイル同期するように設定を行う必要がありますし、ssh鍵などのSecret情報も同期したりする必要があります。

本格的に開発や検証を行う用途の場合はVirtual BoxでもOKですが、保守運用などの作業メンバーがちょっとした作業のためにDockerが必要なユースケースの場合にはVirtual Boxはあまりにも仰々しくてお勧めしづらい側面があります。
(今回の記事を書いたのも、実用的、かつ敷居の低い代替手段が必要だったためです)