Dockerで開発用デスクトップ環境（GUI）を構築する

# はじめに

今年からDockerを使い始めた未熟者です。

- 運用をDockerコンテナで行うので、開発も似た環境で行った方が良いかな？
- 開発時はGUIのツールを使いたい。

そんな単純な動機で、Dockerで開発用デスクトップ環境の構築を試みることにしました。

## 以下の順に試しました

### (1) ホストにX Serverをインストールして、DockerコンテナからホストにXを飛ばす
#### 結果
- ホストがWindowsのとき（X ServerはMobaXterm）：成功したが動きがやや重い。  
- ホストがMacのとき（X ServerはXQuartz）：Xを飛ばせたが、[PyCharm](https://www.jetbrains.com/pycharm/)を操作中にフリーズした。

### (2) VNC接続
#### 結果
成功したが動きがやや重い。
ホストとのクリップボード経由のコピペは、

- 「VNCデスクトップ→ホスト」は成功
- 「ホスト→VNCデスクトップ」は失敗

### (3) XRDP
#### 結果
**成功したので採用。** 動きも一番軽い。
クリップボード経由のコピペは両方向OK。

#### XRDPの参考ページ（感謝します）
[Dockerでuid/gid指定可能かつsudo使用可能なデスクトップ環境を構築する(XRDP編)](https://qiita.com/yama07/items/b905ceff0498e52b00cb)

参考ページではコンテナ環境を毎回使い捨てますが、環境をいったん保存して再開したいこともあると思います。
例えば、sudoを使ってホームディレクトリ以外を変更するような作業中に、

  - リモートデスクトップの調子がおかしくなったので`docker restart`したい。
  - 定時になったので、電源を落として翌日作業の続きを行いたい。

そこで、`docker run`後に`docker stop`で停止しても、`docker start`で再開できるように実装してみました。

# Dockerfile

基本的に[参考ページ](https://qiita.com/yama07/items/b905ceff0498e52b00cb)と同じです。変更したのは冒頭の3箇所のみですので、Dockerfile全体は再喝しません。

### (1) Ubuntuのバージョンを18.04に上げた
```
FROM ubuntu:18.04
```

### (2) apt-getをaptに変更した
```
RUN apt update \
    && DEBIAN_FRONTEND=noninteractive apt install -y \
```

### (3) apt installにsudoのインストールを追加した（Ubuntu 18.04では必要でした）
```
RUN apt update \
    && DEBIAN_FRONTEND=noninteractive apt install -y \
      sudo \
      lxde \
```

# endpoint.sh

docker run時にユーザ作成をendpoint.shで行う点は[参考ページ](https://qiita.com/yama07/items/b905ceff0498e52b00cb)と同じですが、docker start時は既存ユーザをそのまま使うように実装してみました。

```endpoint.sh
#!/bin/bash -eu

USER_ID=$(id -u)
GROUP_ID=$(id -g)
PASSWD=${PASSWD:-${DEFAULT_PASSWD}}
USER=${USER:-${DEFAULT_USER}}

# グループを作成する
echo "GROUP_ID: $GROUP_ID"
if [ x"$GROUP_ID" != x"0" ]; then    # rootグループでないとき
    # gidが既にあるか調べて、なければグループ作成
    grep ":${GROUP_ID}:" /etc/group || groupadd -g $GROUP_ID $USER
fi

# ユーザを作成する
echo "USER_ID: $USER_ID"
if [ x"$USER_ID" != x"0" ]; then    # rootでないとき
    export HOME=/home/$USER

    # uidが既にあるか調べる
    set +e
    grep ":${USER_ID}:${GROUP_ID}:" /etc/passwd
    RET=$?
    set -e

    # uidがなければ（= docker run）ユーザ作成、あれば（= docker start）何もしない
    if [ $RET -ne 0 ]; then
        # docker runするときに、ホストのディレクトリ等をコンテナのホームディレクトリに
        # マウントさせると、コンテナ内に既に${HOME}ディレクトリが存在して、マウントした
        # ボリュームが見えるため、useradd -m すると以下の警告が出る。
        #     useradd: warning: the home directory already exists.
        #     Not copying any file from skel directory into it.
        # その場合、まだ${HOME}に/etc/skel配下のファイルがなければ、
        # ${HOME}にコピーし、あればそのまま利用する。
        useradd -d ${HOME} -m -s /bin/bash -u $USER_ID -g $GROUP_ID $USER
        [ ! -e ${HOME}/.xsession ] && cp -r /etc/skel/. ${HOME}

        # ログインパスワードを設定する
        echo "PASSWD: $PASSWD"
        echo ${USER}:${PASSWD} | sudo chpasswd
    fi
else
    # ログインユーザ名をrootにする
    USER=$(whoami)
    echo "USER: $USER"

    # ログインパスワードを上書きする
    echo "PASSWD: $PASSWD"
    echo ${USER}:${PASSWD} | chpasswd
fi

# パーミッションを元に戻す
sudo chmod u-s /usr/sbin/useradd
sudo chmod u-s /usr/sbin/groupadd

[ ! -e ${HOME}/.xsession ] && cp /etc/skel/.xsession ${HOME}/.xsession

echo "#############################"
echo "USER: $USER"
echo "HOME: $HOME"

# XRDPサーバを起動する
# 下の1行がないと、docker start時にxrdp接続が失敗した
[ -e /var/run/xrdp/xrdp.pid ] && sudo rm /var/run/xrdp/xrdp.pid
# docker start時のxrdp接続で、下の行でstart：失敗、restart：成功
sudo bash -c "/etc/init.d/xrdp restart && tail -F /var/log/xrdp-sesman.log"
```

# docker build

Dockerfileとendpoint.shがあるディレクトリで、以下を実行します。

```
docker build -t lxde_xrdp:ubuntu18.04_ja .
```

# docker run
通常は、（rootでなく）一般ユーザのユーザIDを渡してコンテナを立ち上げます。docker runのオプションの意味は、[参考ページ](https://qiita.com/yama07/items/b905ceff0498e52b00cb)をご参照ください。

```
# コンテナのホームディレクトリにマウントするホスト側のディレクトリを作成する。
mkdir $HOME/container_lxde_xrdp_$(whoami)_home

# コンテナ立ち上げ例
docker run -it \
    --privileged \
    --name lxde_xrdp_$(whoami) \
    --mount type=bind,src=${HOME}/container_lxde_xrdp_$(whoami)_home,dst=/home/$(whoami) \
    -p 3389:3389 \
    -u $(id -u):$(id -g) \
    -e USER=$(whoami) \
    -e PASSWD=p@ss \
    lxde_xrdp:ubuntu18.04_ja
```

Webアプリ開発用に例えば5000番ポートを開けるには、下記のようにdocker runします。

```
docker run -it \
    --privileged \
    --name lxde_xrdp_$(whoami) \
    --mount type=bind,src=${HOME}/container_lxde_xrdp_$(whoami)_home,dst=/home/$(whoami) \
    -p 3389:3389 \
    -p 5000:5000 \
    -u $(id -u):$(id -g) \
    -e USER=$(whoami) \
    -e PASSWD=p@ss \
    lxde_xrdp:ubuntu18.04_ja
```

このとき、ブラウザからは以下のアドレスにアクセスします。

```
http://localhost:5000/
```

# docker stop、docker start

コンテナ停止

```
docker stop lxde_xrdp_$(whoami)
```

停止したコンテナを再開

```
docker start -i lxde_xrdp_$(whoami)
```