DockerからChromeをホストPCにX11で表示する方法: Pulseaudioで音声サポート付き

## はじめに
本記事では、Dockerを使用してUbuntu 24.04のコンテナ内でGoogle Chromeをインストールし、ホスト側のXサーバーでブラウザを表示させ、音声出力もサポートする方法について解説する。最初に手動で行う手順を説明し、その後にこれをDockerfileとして自動化する方法を示す。

## ホストのXサーバーでGoogle Chromeを表示させる

### Dockerコンテナの起動（Xと音声サポート付き）

Ubuntu24.04のイメージをホストPC側のXサーバーでの描画とPulseAudioの音声出力をサポートするための複数のオプションをつけてコンテナを起動する。


```bash:ホストPCで実行
docker run -it --rm \
  --name ubuntu24 \
  --env="DISPLAY=$DISPLAY" \
  --env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native" \
  --volume="$HOME/.Xauthority:/root/.Xauthority:rw" \
  --volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native" \
  --volume="/etc/machine-id:/etc/machine-id:ro" \
  --volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse" \
  --network=host \
  ubuntu:24.04 bash
```

#### オプションの詳細

- **`docker run`**: Dockerコンテナを起動するためのコマンド

- **`-it`** 
  - `-i`: コンテナが標準入力（`stdin`）を受け付ける
  - `-t`: 仮想端末を割り当てコマンドラインインターフェースでの対話を可能にする

- **`--rm`**
  - コンテナの実行が終了した際に、そのコンテナを自動的に削除する

- **`--name ubuntu24`** 
  - コンテナに`ubuntu24`という名前を付ける

- **`--network=host`** 
  - コンテナがホストのネットワークスタックを使用するようにします。コンテナとホストが同じネットワークインターフェースとIPアドレスを共有しX11やPulseAudioなどローカルホスト経由での通信が必要な場合に必要

- **`ubuntu:24.04`** 
  - `ubuntu:24.04`は、使用するDockerイメージを指定する

- **`bash`** 
  - `bash`は、コンテナ内で起動するコマンド（この場合はBashシェル）を指定する


##### ホストPCのXサーバーで描画するためのオプション指定

- **`--env="DISPLAY=$DISPLAY"`**
  - ホストシステムの`DISPLAY`環境変数をコンテナに渡す

- **`--volume="$HOME/.Xauthority:/root/.Xauthority:rw"`** 
  - ホストの`.Xauthority`ファイルをコンテナにマウントします。このファイルにはXサーバーの認証情報を含む。コンテナ内のアプリケーションがホストのXサーバーに接続するための設定
  - `rw`は読み書き権限


- **`--env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native"`** 
  - ホストシステムのPulseAudioサーバーに接続する。`PULSE_SERVER`環境変数にPulseAudioサーバーのソケットパスを設定し、コンテナ内のアプリケーションがホストのオーディオ出力を利用する

##### ホストPCに音声を再生するためのオプション指定

- **`--volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native"`** 
  - ホストのPulseAudioサーバーのUNIXソケットをコンテナにマウントする。コンテナ内のアプリケーションがこのソケットを介してホストのPulseAudioサーバーに接続する

- **`--volume="/etc/machine-id:/etc/machine-id:ro"`** 
  - ホストの`/etc/machine-id`ファイルをコンテナにマウントする。このファイルはシステム固有の識別子を含む。コンテナ内のアプリケーションがホストのサービスにアクセスする
  - `ro`は読み取り専用

- **`--volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse"`** 
  - ホストのユーザーランタイムディレクトリ内のPulseAudioソケットをコンテナにマウントする。コンテナ内のアプリケーションがこのソケットを利用してホストのオーディオサービスにアクセスする





### コンテナ内でGoogle Chromeをインストール

必要なパッケージを含めてGoogle Chromeをインストールする。

```bash: Docker内で実行
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y wget 
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt-get install -y \
    ./google-chrome-stable_current_amd64.deb \
    libcanberra-gtk-module \
    libcanberra-gtk3-module \
    fonts-ipafont-gothic \
    fonts-ipafont-mincho \
    fonts-noto-cjk \
    language-pack-ja \
    pulseaudio \
    socat \
    alsa-base \
    --no-install-recommends && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
```


### dbusエラーを回避する
```bash:Docker内で実行
# D-Busサービスを開始
/etc/init.d/dbus start

# XDG_RUNTIME_DIR環境変数を設定
export XDG_RUNTIME_DIR=/run/user/$(id -u)

# 適切な権限でランタイムディレクトリを作成
mkdir -p $XDG_RUNTIME_DIR
chmod 700 $XDG_RUNTIME_DIR
chown $(id -un):$(id -gn) $XDG_RUNTIME_DIR

# DBUS_SESSION_BUS_ADDRESS環境変数を設定
export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus

# D-Busセッションデーモンを開始
dbus-daemon --session --address=$DBUS_SESSION_BUS_ADDRESS --nofork --nopidfile --syslog-only &
```

### X11フォワーディングの許可

ホストのXサーバーが外部接続を許可しているか確認する。必要に応じて以下のコマンドを**ホスト側のPC**で実行する。

```bash:ホストPCで実行
xhost +
```

### DISPLAY環境変数を設定

```bash:Docker内で実行
# 環境変数の確認
echo $DISPLAY

# $DISPLAYに :0 などが適切に設定されていない場合は手動で設定する
export DISPLAY=:0
```

### Google Chromeの起動

Google Chromeを起動してホスト側のXサーバーで表示する。


```bash:Docker内で実行
# Google Chromeブラウザを起動する
google-chrome --no-sandbox --disable-gpu --disable-dev-shm-usage
```

#### オプションの解説

1. **`--no-sandbox`**
   - docker内で root で google-chrome を実行する場合に必要となる

2. **`--disable-gpu`**
   - GPUアクセラレーションを無効にする。Dockerコンテナ内でChromeを実行する際、ホストのGPUを適切に利用できない場合がある。このオプションを使用することで、GPUに依存する処理（ハードウェアアクセラレーション）を無効にし、代わりにCPUでレンダリングを行う。GPU関連のエラーや問題を回避する。

3. **`--disable-dev-shm-usage`**
   - `/dev/shm`（共有メモリ）を使用せず、代わりにディスク上の一時ファイルを使用する。Dockerコンテナ内の`/dev/shm`（共有メモリ）はデフォルトでサイズが制限されており（通常は64MB）、これによりChromeが大量のメモリを必要とする処理（例えば、大きなウェブページのレンダリング）が失敗する。このオプションを使用することで、`/dev/shm`の制限を回避し、Chromeが安定して動作する

## トラブルシューティング


### Xのフォワーディングの確認

Docker内で起動したxeyesがホスト側のXサーバーで表示されることを確認する

```bash:Docker内で実行
apt-get update
apt-get install -y x11-apps
xeyes
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/6efc8705-8786-e8fe-58ca-e1a37aa3cb89.png)


### 音声再生の確認

Docker内で音声再生が正常に動作しているか確認するために、以下のコマンドを使用する。

```bash:Docker内で実行
# サンプル音源をダウンロード
wget https://freewavesamples.com/files/1980s-Casio-Piano-C5.wav

# サンプル音声を再生
apt-get update
apt-get install -y alsa-utils
aplay 1980s-Casio-Piano-C5.wav
```

音声が正常に再生されれば成功


## 手順の自動化

これまで手動で行った手順をDockerfileにまとめ自動化する

### Dockerfileの作成

```bash:ホストPCでDockerfileを作成する
# 適当なワークディレクトリに移動
mkdir -p /tmp/test
cd /tmp/test

# 上記のDockerfileの内容をコピペして作成
vi Dockerfile
```

```Dockerfile:Dockerfile
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive
ENV DISPLAY=:0
ENV PULSE_SERVER=unix:/run/user/1000/pulse/native

RUN apt-get update && \
    apt-get install -y wget pulseaudio socat alsa-base \
    libcanberra-gtk-module libcanberra-gtk3-module \
    fonts-ipafont-gothic fonts-ipafont-mincho fonts-noto-cjk \
    language-pack-ja dbus-x11 && \
    wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
    apt-get install -y ./google-chrome-stable_current_amd64.deb --no-install-recommends && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# D-Busの設定と起動を行うスクリプトを作成
RUN echo "#!/bin/bash\n\
/etc/init.d/dbus start\n\
export XDG_RUNTIME_DIR=/run/user/\$(id -u)\n\
mkdir -p \$XDG_RUNTIME_DIR\n\
chmod 700 \$XDG_RUNTIME_DIR\n\
chown \$(id -un):\$(id -gn) \$XDG_RUNTIME_DIR\n\
export DBUS_SESSION_BUS_ADDRESS=unix:path=\$XDG_RUNTIME_DIR/bus\n\
dbus-daemon --session --address=\$DBUS_SESSION_BUS_ADDRESS --nofork --nopidfile --syslog-only &\n\
exec \"\$@\"" > /start.sh && chmod +x /start.sh

CMD ["/start.sh", "google-chrome", "--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage"]
```



このDockerfileを使用することで、コンテナを簡単にビルドして音声サポート付きのGoogle Chromeを実行できる


#### Dockerイメージの作成

```bash:ホストPCで実行
# chrome起動用のDockerイメージを作成
docker build -t ubuntu24_chrome .

# イメージの作成を確認
docker images
REPOSITORY        TAG       IMAGE ID       CREATED          SIZE
ubuntu24_chrome   latest    48318381cbff   50 seconds ago   1.23GB
ubuntu            24.04     35a88802559d   2 months ago     78.1MB
ubuntu            latest    35a88802559d   2 months ago     78.1MB
```

#### Dockerイメージの実行

作成したDockerイメージ(ubuntu24_chrome)を実行する

```bash:ホストPCで実行
docker run -it --rm \
  --name ubuntu24_chrome \
  --env="DISPLAY=$DISPLAY" \
  --env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native" \
  --volume="$HOME/.Xauthority:/root/.Xauthority:rw" \
  --volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native" \
  --volume="/etc/machine-id:/etc/machine-id:ro" \
  --volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse" \
  --network=host \
  ubuntu24_chrome
```

必要に応じて以下のコマンドを**ホスト側のPC**で実行しておく

```bash:ホストPCで実行
xhost +
```

## まとめ

Dockerを利用して、ホストのXサーバーでGoogle Chromeを表示し、音声出力もサポートする手順を説明した。最終的には、これらの手順をDockerfileにまとめて自動化した。

## ハードウェアアクセラレーションのサポート

次回はDockerでGPUを有効にしてハードウェアアクセラレーションをサポートでWebGLの高速描画を実現する

https://qiita.com/rairaii/items/398541a16dccc608fc9a

## `xhost +`を使わずに実現する方法
https://qiita.com/rairaii/items/879f5d23d96b1053417e