Flutter in Docker - Dockerコンテナ内にFlutter開発環境を構築して利用する方法

# 1. はじめに

Flutterの開発環境をDockerコンテナ (今回はUbuntu 20.04 LTS) に構築し、macOSやLinux OSのHost PCからあたかもHost PC上で開発しているのと同じような環境を構築する方法を紹介します。この仕組みを利用すれば、リモート (サーバー) に開発環境を構築し、それにローカルPCからアクセスしてFlutterアプリの開発が可能です。
<img width="2337" alt="スクリーンショット 2020-07-30 22.15.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/b077f22b-155f-8d77-7cfe-2d30b0439695.png">

## Flutter in Docker全体像

(補足) 今回はトライしていませんが、Dockerコンテナ内にAndroid版 (Aundroid Studioを利用したエミュレータ上での動作) も原理上は動作可能なはずです。

<img width="640" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/7d28c784-b31e-e7a2-0750-3cff3b59487d.png">

## Dockerコンテナ化するメリット (例)
* 開発環境を複製・削除し易く、同じ環境を第三者に配ることが可能
* 環境をDockerコンテナ化しておくとCI/CD時に便利
* OSに依存せず (Linux or macOS) 、Android/iOS以外の環境での動作確認が可能

## ターゲット (Host PC) 環境

* macOS
* Linux OS (Flutter, Dockerが動作する64bit Linux環境であれば基本的にどのディストリービューションでもOK) 

# 2. Dockerインストール

Docker自体のインストール方法については割愛しますが、基本的に公式サイトの手順通りに進めれば大丈夫です。

* macOS: [Install Docker Desktop on Mac](https://docs.docker.com/docker-for-mac/install/)
* Linux (Ubuntu): [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)

# 3. Dockerコンテナイメージの作成

Dockerコンテナイメージを作成する手順について説明します。Dockerコンテナ内のOS (rootfs) はUbuntu 20.04にしました。今回はDockerfileは利用せず、全て手動による手順での説明です。

### Ubuntu 20.04 イメージ (rootfs) の取得

```
$ docker pull ubuntu:20.04
```

### Dockerコンテナの起動

以下のコマンドでDockerコンテナを起動してそのコンテナ中に入ります。基本的にセキュリティなし (`--privileged`) で、Host PCのOSと同じ権限でアクセス出来るようにします。

なお、ここで指定したパラメータを後述のDocker-composeの設定ファイルでも利用するため、まずはこの設定のまま実行して下さい。

```
$ docker run -it --name flutter-docker ¥
     --privileged -h flutter -u root -w /root ¥
     --add-host=flutter:127.0.1.1 --net=host ubuntu:20.04
```

問題がなければプロンプトが`#`に変わり、Dockerコンテナ内の操作に切り替わっています。例えば、`uname`コマンドを打つとLinux OSになっていることが分かります。

```
# root@flutter:~# uname -a
Linux flutter 4.19.76-linuxkit #1 SMP Tue May 26 11:42:35 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
```

### 必要なツールのインストール

Dockerコンテナ内のUbuntu OSに必要なパッケージをインストールします。`xserver-xorg`のインストール時にはロケールやキーボードの情報を求められますが、適当で良いです (無難ならUS) 。

```
# apt update
# apt install git unzip clang xserver-xorg pkg-config libgtk-3-dev curl cmake ninja-build
```

### Chromeブラウザのインストール (必要があれば)

```
# apt install wget gnupg
# sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
# wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
# apt update
# apt-get install google-chrome-stable
```

参考: https://qiita.com/pyon_kiti_jp/items/e6032eb6061a4774aece

### Flutter SDKのインストール

Flutter SDKをgit cloneして、/opt以下に配置します。

```
# git clone https://github.com/flutter/flutter.git
# mv flutter /opt/
```

### コンテナイメージの保存

ここまででコンテナ内での作業は全て終了です。なので、コンテナから抜けます。

```
# exit
```

作成したコンテナをDockerイメージファイルとして保存します。ここで指定したパラメータを後述のDocker-composeの設定ファイルでも利用するため、まずはこの設定のまま実行して下さい。

```
$ docker commit flutter-docker flutter-docker/ubuntu:latest
```

# 4. Host PC側の設定 (macOSの場合のみ)

Dockerコンテナ内のGUIアプリケーションはX11プロトコルで動作するため、そのGUIアプリの画面をHost PC側のX11サーバーに転送する必要があります。macOSの場合、Host側のX11サーバーとしてXQuartzがありますので、それをインストールして利用します。

## XQuartzのインストール

```
$ brew cask install xquartz
```

Host PCを再起動します。再起動後、`DISPLAY`環境変数が以下のように設定されていることを確認します。

```
$ echo $DISPLAY
/private/tmp/com.apple.launchd.NagCeWDLYl/org.macosforge.xquartz:0
```

DockerコンテナのX11クライアントからのアクセスを許可します。

```
$ xhost +$(hostname)
$ xhost + local:root
```

コマンドラインもしくは、LaunchPadから`XQuartz`を立ち上げておきます。
<img width="278" alt="スクリーンショット 2020-07-31 0.41.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/04e98687-1b7f-bd7d-c5cc-47a90de17e9a.png">


# 5. Host PC側の設定 (Linux OSの場合のみ)

Linux OSの場合は大抵がX11デフォルトだと思いますので、以下の対応だけを行います。

```
$ xhost + local:root
```

# 6. VSCode環境構築

VSCodeのインストールとVSCodeからDockerコンテナを利用するためのExtensions等をインストールします。

## VSCodeのインストール

以下を参考にしてインストールしてください。

* Linux (Ubuntu): https://qiita.com/yoshiyasu1111/items/e21a77ed68b52cb5f7c8
* macOS: https://qiita.com/watamura/items/51c70fbb848e5f956fd6

## VSCode Extensionsインストール

以下の3つのExtensionsをインストールします。

<img width="777" alt="スクリーンショット 2020-07-30 23.44.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/ec0016df-48e7-b454-8178-fd22194388df.png">

<img width="865" alt="スクリーンショット 2020-07-30 20.25.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/4f332809-655b-e98e-59ce-67a06ac4f6e1.png">

<img width="865" alt="スクリーンショット 2020-07-30 20.25.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/1d273183-6c5e-deee-2ca1-8dd19dbf061b.png">

# 7. Docker Composeファイルの作成

VSCodeからDockerコンテナ内のFlutter SDKを利用するために、Docker Composeと先ほどインストールしたVSCode Extensionsの`Remote Development`の設定ファイルを作成します。作成したファイルは https://github.com/Kurun-pan/flutter-docker に置いているため、Host OS (Linux or macOS) に合わせて利用して下さい。

```
$ mkdir flutter_docker
$ cd flutter_docker
```

カレントディレクトリ (flutter_docker) 以下に`docker-compose.yml`ファイルと`.devcontainer/devcontainer.json`ファイルの2つを作成します。
<img width="195" alt="スクリーンショット 2020-07-30 23.50.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/a3eed427-bcde-ed2e-7c7d-f3332373a91f.png">

## Host OSがmacOSの場合

`DISPLAY`環境変数に`host.docker.internal:0`を設定するのがポイントのはずでしたが、上手く行かなかったため、`{write your host name!!}`の部分はmacOSのhostnameを指定してください。。誰か正しい方法を知っていれば教えて下さい。

https://github.com/Kurun-pan/flutter-docker/tree/master/macos

```yaml:docker-compose.yml
version: "3"
services:
    flutter:
        image: flutter-docker/ubuntu:latest
        working_dir: /root/workspace
        command: sleep infinity
        environment:
            - HOME=/root
            - no_proxy=127.0.0.1,localhost
            #- DISPLAY="host.docker.internal:0"
            - DISPLAY={write your host name!!}:0
        volumes:
            - ~/.gitconfig:/home/root/.gitconfig
            - ./:/root/workspace
            - ~/.Xauthority:/root/.Xauthority
        network_mode: "host"
        extra_hosts:
            - flutter:127.0.1.1
```

```json:.devcontainer/devcontainer.json
{
    "name": "Flutter docker",
    "dockerComposeFile": [
        "../docker-compose.yml",
    ],
    "service": "flutter",
    "remoteUser": "root",
    "remoteEnv": {
        "QT_X11_NO_MITSHM": "1",
        "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/flutter/bin",
    },
    "settings": {
        "terminal.integrated.shell.linux": null
    },
    "runArgs": [
        "--privileged",
        "-P",
    ],
    "extensions": ["dart-code.flutter"],
    "workspaceMount": "source=${localWorkspaceFolder}/workspace,target=/root/workspace,type=bind,consistency=delegated",
    "workspaceFolder": "/root/workspace"
}
```

## Host OSがLinuxの場合

Linux OSの場合、OpenGLでDRM (DRI) をDockerコンテナ内のFlutterアプリが利用できるように、`/dev/dri`をbind-mountします。

https://github.com/Kurun-pan/flutter-docker/tree/master/linux

```yaml:docker-compose.yml
version: "3"
services:
    flutter:
        image: flutter-docker/ubuntu:latest
        working_dir: /root/workspace
        command: sleep infinity
        environment:
            - HOME=/root
            - no_proxy=127.0.0.1,localhost
        volumes:
            - ~/.gitconfig:/home/root/.gitconfig
            - ./:/root/workspace
            - /tmp/.X11-unix:/tmp/.X11-unix
        devices:
            - /dev/dri:/dev/dri
        network_mode: "host"
        extra_hosts:
            - flutter:127.0.1.1
```

Host OSの`DISPLAY`環境変数を引継ぎます。

```json:.devcontainer/devcontainer.json
{
    "name": "Flutter docker",
    "dockerComposeFile": [
        "../docker-compose.yml",
    ],
    "service": "flutter",
    "remoteUser": "root",
    "remoteEnv": {
        "QT_X11_NO_MITSHM": "1",
        "DISPLAY": "${localEnv:DISPLAY}",
        "XAUTHORITY": "/tmp/.X11-unix",
        "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/flutter/bin",
    },
    "settings": {
        "terminal.integrated.shell.linux": null
    },
    "runArgs": [
        "--privileged",
        "-P",
    ],
    "extensions": ["dart-code.flutter"],
    "workspaceMount": "source=${localWorkspaceFolder}/workspace,target=/root/workspace,type=bind,consistency=delegated",
    "workspaceFolder": "/root/workspace"
}
```

# 8. Flutter in Dockerの実行

## VSCode立ち上げ
`docker-compose.yml`が存在するディレクトリでVSCodeを立ち上げます。

```
$ code .
```

## VSCode Remote Containerのオープン

ウインドウ左下の`><`の部分をクリックもしくは、`shift+alt+p`でコマンドパレットを開き、`Remote-containers: Open Folder in Container...`を選択します。
<img width="1215" alt="スクリーンショット 2020-07-31 0.26.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/98c8bbdb-91d3-aa7b-95ff-610f7dae99f0.png">

Openをクリックします。
<img width="1215" alt="スクリーンショット 2020-07-31 0.26.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/fdd66382-e622-936b-d857-ba73e2819a0f.png">

VSCodeウインドウ下部にターミナルが開きます。このターミナル操作対象は作成したDockerコンテナイメージ (flutter-docker/ubuntu:latest) です。
<img width="1215" alt="スクリーンショット 2020-07-31 0.29.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/3ac82c22-bfba-a028-f314-1d3f496e7d9b.png">

## Flutterサンプルアプリのプロジェクト作成

ここからVSCodeのターミナル (Dockerコンテナ) で以下のコマンドを実行し、Flutterサンプルアプリを実行します。

```
# flutter config --enable-linux-desktop
# flutter config --enable-web
# mkdir sample
# cd sample
# flutter create .
```

<img width="1215" alt="スクリーンショット 2020-07-31 0.31.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/075038c5-6388-61d8-bdca-d2a629290b6e.png">

## Flutterサンプルアプリの実行

### サンプルアプリ for Linux Desktop

```
# flutter run -d linux
```

<img width="1302" alt="スクリーンショット 2020-07-31 0.47.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/cee01d65-65ec-8152-0b82-51f45dbdcfcf.png">

コマンドを実行する以外には、VSCodeでdartソースコードを開いて、GUI操作でもアプリ実行やブレークポイントを利用したデバッグ等が可能です。
<img width="1888" alt="スクリーンショット 2020-07-31 1.22.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/bbd42df2-5ea1-7f9b-0782-76c1bdeed048.png">

### サンプルアプリ for Web

対応中なので少々お待ちを。。

```
# flutter run -d chrome
```