🏗️

GitHub Codespaces上でdocker compose upしたい

2022/07/21に公開2

はじめに

GitHub Codespaces便利ですよね。

.devcontainer ディレクトリに色々ちゃんと整備しておくと安定した開発環境を多人数で共通で立ち上げられるところが便利です。

そんな便利なGitHub Codespaces上で docker-compose.yml を使って docker compose up して複数サービスを立ち上げたいと思ったことはありませんか?
(たとえばAPIサーバーとフロントエンドとDBと、などなど…)

GitHub Codespacesは実態はDockerコンテナ内なので、その内部で docker compose up するのは少しコツが必要でした。

この記事ではGitHub Codespaces上で docker compose up する方法を紹介します。

最小限のデモはこちら(実際にGitHub Codespacesで動作確認しました):
https://github.com/yuiseki/devcontainer-node-vite

.devcontainer/ 以下のファイル

.devcontainer/devcontainer.json

各項目の詳細は以下の公式ドキュメントを見てください
https://code.visualstudio.com/docs/remote/devcontainerjson-reference

{
  "name": "Docker from Docker",

  // .devcontainer/Dockerfile を指定します
  "dockerFile": "./Dockerfile",

  // Windows 10 Proだと管理者権限でVSCodeを起動しないと
  // port 3000や5000が使えなくて罰なので
  // port 30000とか50000にしています……
  "forwardPorts": [30000, 50000],

  // devcontainerのユーザーをroot以外にします(推奨)
  "remoteUser": "user",

  // devcontainer起動時にdocker runに渡されるオプション
  "runArgs": ["--init"],

  // Docker from Dockerを実現するためにはこれが必要です
  "mounts": [
    "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
  ],

  // これを指定しないとDockerfileのCMDが勝手に
  // "while sleep 1000; do :; done"
  // に書き換えられます(マジ)
  "overrideCommand": false,

  // Docker from Dockerでホストファイルをマウントするために必要です
  "remoteEnv": {
    "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
  },

  // 私はGNUを信仰しているのでデフォルトのシェルをbashにしていますがここはお好みでどうぞ
  // ただしデフォルトのシェルを指定しないと/bin/shになって結構過酷です
  "customizations": {
    "vscode": {
      "settings": {
        "terminal.integrated.profiles.linux": {
          "bash": {
            "path": "/bin/bash"
          }
        },
        "terminal.integrated.defaultProfile.linux": "bash"
      }
    }
  }
}

.devcontainer/Dockerfile

アプリケーションのDockerfileをそのままdevcontainerのDockerfileに指定することもできますが、分けることをオススメします。

特にDocker from Dockerを実現する場合、以下のようにグチャグチャやる必要が生じるので。。

グチャグチャやってる部分に関しては以下のドキュメントからコピペしただけです:
https://github.com/microsoft/vscode-dev-containers/tree/main/containers/docker-from-docker#enabling-non-root-access-to-docker-in-the-container

# 開発用コンテナなのでubuntuでええやろ
FROM ubuntu:22.04

# 開発環境に必要なパッケージなどはここでインストールしておきます
# gitはGitHub Codespacesで必須です
# curlも次のステップで使うので必須です
RUN apt update && apt install -y \
    git \
    curl \
    vim \
    htop \
    jq

# Docker from DockerするためにDockerをインストールします
RUN curl -fsSL https://get.docker.com | sh

# たとえばNode.jsのプロジェクトならここで開発用コンテナにもインストールしておく
RUN curl -Ls https://deb.nodesource.com/setup_18.x | bash
RUN apt update && apt install -y nodejs

# この二行なくてもdevcontainer.jsonのforwardPortsが優先されるのかもしれないけど一応書いてる
EXPOSE 30000
EXPOSE 50000

# 実際に開発に使うroot以外のユーザーを作成します
RUN useradd -m user

# dockerコマンドを上記のroot以外のユーザーでも使えるようにするためにグチャグチャやってます
ARG NONROOT_USER=user
RUN echo "#!/bin/sh\n\
    sudoIf() { if [ \"\$(id -u)\" -ne 0 ]; then sudo \"\$@\"; else \"\$@\"; fi }\n\
    SOCKET_GID=\$(stat -c '%g' /var/run/docker.sock) \n\
    if [ \"${SOCKET_GID}\" != '0' ]; then\n\
        if [ \"\$(cat /etc/group | grep :\${SOCKET_GID}:)\" = '' ]; then sudoIf groupadd --gid \${SOCKET_GID} docker-host; fi \n\
        if [ \"\$(id ${NONROOT_USER} | grep -E \"groups=.*(=|,)\${SOCKET_GID}\(\")\" = '' ]; then sudoIf usermod -aG \${SOCKET_GID} ${NONROOT_USER}; fi\n\
    fi\n\
    exec \"\$@\"" > /usr/local/share/docker-init.sh \
    && chmod +x /usr/local/share/docker-init.sh

ENTRYPOINT [ "/usr/local/share/docker-init.sh" ]
CMD [ "sleep", "infinity" ]

アプリケーションのDockerfileとdocker-compose.yml

簡単のために一つのserviceを起動するdocker-compose.ymlにしますが、
複数のserviceを起動する場合でもちゃんと動きます。

frontend というディレクトリにViteプロジェクトがあるという想定にしてみましょう。

npm create vite@latest frontend -- --template react-ts
cd frontend
npm i

frontend/Dockerfile

これは普通に書いてOKです。

たとえばViteのプロジェクトなら

FROM node:18-bullseye-slim

WORKDIR /app
COPY . /app

RUN npm ci

EXPOSE 30000

CMD ["npm", "run", "dev"]

みたいなもので良いでしょう。

frontend/package.json

npm run dev で起動する際にport, hostを指定するように package.json を書き換える必要があります。

  "scripts": {
    "dev": "vite --port 30000 --host 0.0.0.0",
    "build": "tsc && vite build",
    "preview": "vite preview --port 30000 --host 0.0.0.0"
  },

Node.js / Vite以外の他のプログラミング言語やフレームワークでも、同様にhostを明示的にbindする必要があります。

docker-compose.yml

開発用に使うdocker-compose.ymlでは、ホスト側のファイルが書き換わったらDockerイメージ上のファイルも書き換わってほしいのでvolumeのbind mountを使うことが多いですが、Docker from Dockerの場合、ここに罠があるので注意が必要です!

version: "3.9"
services:
  frontend:
    build:
      context: frontend
      dockerfile: Dockerfile
    volumes:
      # ここが重要
      # Docker from Dockerでは .:/app と書くと正常に動かない
      # 相対パスではなく、フルパスで書く必要がある
      # 一方でDockerの外でも動かしたいので、
      # デフォルト値として相対パスを指定している
      - type: bind
        source: ${LOCAL_WORKSPACE_FOLDER:-.}/frontend
        target: /app
    command: npm run dev
    ports:
      - 30000:30000
    networks:
      - myapp

networks:
  myapp:
    name: myapp

使い方

  • GitHub上でCodespacesを立ち上げる
  • VSCodeでCodespacesを開く
  • 統合ターミナルを立ち上げる
  • docker compose up
  • http://localhost:30000/ を開く

Discussion