COLOPL Tech Blog

コロプラのエンジニアブログです

LLM 時代だからこそ!改めて見直す Development Containers

こんにちは、エンジニアの 工藤 です。

皆さん、 Development Containers はご存知でしょうか? LLM 時代よりも前から既にバリバリ活用されている方も居れば、 LLM を安全に使うために使い始めた人、使ったほうが良いとは思いつつもまだ手を付けられていない人なども多いのではないかと思います。

Development Containers はプロジェクトの開発環境をコンテナ上に構築することで環境依存を最小限に抑え、かつローカルマシンに影響を及ぼさない形でサンドボックス化して隔離する仕組みです。開発環境のセットアップも自動化されるため、プロジェクトのオンボーディングにも有用で保守性を高めやすいメリットがあります。また、 macOS や Windows を利用している場合であっても、コンテナ上であるため本物の Linux 上で開発を行える、というメリットもあります。

コロプラでは colopressoPHP Extension などの開発を通じ、以前から Development Containers (以下: DevContainers) を活用した開発をしてきました。今回は 今すぐ始められる DevContainers 入門 というような形で、今すぐにでも DevContainers を試せるハンズオン的なチュートリアルをお届けしたいと思います。


DevContainers を使ってみよう

1. Dockerfile を作成する

DevContainers は名前の通り、 コンテナ上に開発環境を構築し、そこで作業する というのが基本となっています。そのため、自分が開発に用いるツールチェーン等をセットアップするための Dockerfile をまず作成することになります。

例えば PHP 8.5 上で Laravel を用いて Web アプリケーションを開発するとします。 Dockerfile を作成していきましょう。だいたいこんな感じになるでしょうか。

FROM php:8.5-fpm AS base

# インタラクティブシェルを無効化
ENV DEBIAN_FRONTEND="noninteractive"

# 必要なパッケージを導入
RUN apt-get update \
 && apt-get install -y --no-install-recommends \
      "ca-certificates" "bash" "ssh" "git" "curl" "unzip" "vim" \
 && docker-php-ext-install "pdo_mysql" \
 && rm -rf /var/lib/apt/lists/*

# Composer を導入, 公式のイメージから取ってくる
COPY --from=composer:2 "/usr/bin/composer" "/usr/local/bin/composer"

2. .devcontainer.json を作成する

DevContainers として利用できるようにするため、定義ファイル (.devcontainer.json) を作成します。これによって DevContainers に対応したエディタ (VSCode, PhpStorm 等) では DevContainers 環境として利用できるようになります。

一旦最小限の定義としてみましょう。

{
  "name": "Test",
  "dockerFile": "./Dockerfile"
}

Visual Studio Code であれば、 Dev Containers 拡張が入っていれば DevContainers として開くことができるはずです。ポップアップが表示されないときは、ウィンドウ左下の >< から コンテナーで再度開く を選択すると開けるかと思います。

では、実際に composer を使って Laravel のプロジェクトを作成してみましょう。

# composer create-project laravel/laravel example
Creating a "laravel/laravel" project at "./example"
Installing laravel/laravel (v13.2.0)
~~~

無事 Laravel プロジェクトが作成されました!では PHP 組み込みの Web サーバー機能を使って実際に実行してみましょう。

# cd example/public
# php -S 127.0.0.1:8931

ブラウザで http://127.0.0.1:8931 を開きます。 Laravel 13 のランディングページが開きました🎉

PHPビルトインサーバーで表示したLaravel 13のランディングページ

3. compose を利用する

ここまでであれば非常にシンプルですが、実際の Web アプリケーションはそうもいきません。というのも通常は永続化されたデータベースや KVS が別に存在しており、かつ PHP のビルトインサーバーは利用しないためです。

一つの Dockerfile で全てをセットアップするのも不可能ではないですが、途端に煩雑になり、メンテナンスしにくくなります。何より本番環境と同一でない構成を取ることは好ましくありません。

というわけで、 docker compose を用いた DevContainers に切り替えていきましょう

まずは compose.yaml ファイルを作成します。今回は次のようにします。突然長くなりますが、気落ちせず行きましょう!

name: test

services:
  # 自前でビルドするアプリコンテナ, 作業にも利用する
  app:
    build:
      context: .
      dockerfile: Dockerfile
    command: ["php-fpm", "-F"]
    working_dir: /workspaces
    volumes:
      # ワークスペースをマウント
      - .:/workspaces:cached
      # 9000 番ポートで PHP-FPM を起動するための設定をマウント
      - ./docker/php/z-devcontainer.conf:/usr/local/etc/php-fpm.d/z-devcontainer.conf:ro
    environment:
      # このあたりの設定は適宜変更してください
      APP_ENV: local
      APP_DEBUG: "true"
      DB_CONNECTION: mysql
      DB_HOST: mysql
      DB_PORT: "3306"
      DB_DATABASE: laravel
      DB_USERNAME: laravel
      DB_PASSWORD: secret
      QUEUE_CONNECTION: sync
      CACHE_STORE: file
      SESSION_DRIVER: file
    depends_on:
      mysql:
        condition: service_healthy
    # nginx からアクセスされる 9000 番ポートを公開
    expose:
      - "9000"

  # MySQL コンテナ
  mysql:
    image: mysql:8.4
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel
      MYSQL_PASSWORD: secret
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 20s

  # nginx コンテナ
  nginx:
    image: nginx:1.29
    depends_on:
      - app
    # ローカルマシンにポートを転送する
    ports:
      - "127.0.0.1:8931:80"
    volumes:
      - .:/workspaces:ro
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro

# MySQL のデータをボリュームで永続化
volumes:
  mysql-data:

次に php-fpm の設定ファイル (docker/php/z-devcontainer.conf)、 nginx の設定ファイル(docker/nginx/default.conf) を作成していきます。

[www]
clear_env = no
catch_workers_output = yes
decorate_workers_output = no
listen = 9000
server {
    listen 80;
    server_name localhost;
    root /workspaces/example/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

最後に .devcontainer.json を編集し、 compose を使うようにします。

{
  "name": "Test",
  "dockerComposeFile": "compose.yaml",
  "service": "app",
  "workspaceFolder": "/workspaces",
  "runServices": ["app", "nginx", "mysql"],
  "forwardPorts": [8931],
  "shutdownAction": "stopCompose",
  "overrideCommand": false
}

これで再度 DevContainers で開き、以下のコマンドを実行します。

# composer create-project laravel/laravel example

http://localhost:8931 にアクセスすると、正常に Laravel のランディングページが表示されました🎉

nginx経由で表示したLaravelのランディングページ

4. LLM でアプリを作ってもらう

.devcontainer.json では、 customizations というキーでエディタ固有の定義ができます。ここでは Claude Code をインストールするようにしてみましょう

{
  "name": "Test",
  "dockerComposeFile": "compose.yaml",
  "service": "app",
  "workspaceFolder": "/workspaces",
  "runServices": ["app", "nginx", "mysql"],
  "forwardPorts": [8931],
  "shutdownAction": "stopCompose",
  "overrideCommand": false,
  "customizations": {
    "vscode": {
      "extensions": [
        "Anthropic.claude-code"
      ]
    }
  }
}

.devcontainer.json の編集が完了したら、 VSCode 左下の >< から コンテナのリビルド を選択します。これでコンテナがリビルドされ、拡張機能がインストールされます。

せっかくなので、現状の Laravel を用いて X モドキな Web アプリをプロンプトで作ってみましょう。

このプロジェクトは Laravel を用いた nginx + PHP-FPM + MySQL 構成です。 X (旧: Twitter) の様に、入力欄とポストボタンが配置され、時系列順にポストが並ぶアプリケーションを作成してください。アカウント管理機能はなくて良く、その代わりに名前入力欄を配置し、投稿された内容は MySQL に読み書きするようにしてください。
Development Container 上で開発しているので、各種設定はそれを参考にしてください。

流石に無条件で勧めることはできませんが、 DevContainers 環境下であれば承認バイパスを検討しやすい状態を作ることができます。 たとえばマウント範囲や資格情報の扱いを適切に整理できていれば、今回のような本番運用環境を含まないローカルモック環境ではほぼリスクはないと言えるでしょう。安全性を担保できていることから、 今回は全てバイパスして作業を進めました。1

LLMで生成したX風Webアプリの画面

こんな感じで X っぽいものが出来ました。 X というよりは掲示板に近いかもしれません。データは MySQL に書き込まれているので、 DevContainers を終了して再度起動してもちゃんとデータは残っています。

DevContainers + LLM = 環境依存が少なく再現しやすい LLM 利用環境

このように、 DevContainers と LLM を適切に利用することで 誰でも開発環境を構築しやすく、条件を整えれば承認バイパスを検討しやすい環境 を作ることができます。

DevContainers はコロプラの OSS でも活用されているので、良ければ参考にしていただければと思います。開発環境を一発で構築できて環境依存が殆ど発生しない、という点だけでも利用する価値はとても高いと感じます。

先日紹介させて頂いた colopresso での DevContainers 利用例 github.com

DevContainers を活用した PHP Extension 開発用のテンプレートプロジェクト github.com

実際にテンプレートプロジェクトを利用して開発している PHP Extension github.com

ぜひ DevContainers を用いて、安全かつ快適な LLM 開発を体験してみてください!



ColoplTechについて

コロプラでは、勉強会やブログを通じてエンジニアの方々に役立つ技術や取り組みを幅広く発信していきます。
connpass および X で情報発信していますので、是非メンバー登録とフォローをよろしくお願いいたします。

また、コロプラではインフラエンジニアを積極採用中です!
興味を持っていただいた方はぜひお気軽にご連絡ください。


  1. たとえば、ワークスペースのマウントは必要最小限にし、可能であれば read-only(読み取り専用)も検討します。 Docker ソケットは共有せず、 SSH 鍵や各種トークンをコンテナに持ち込まないことが重要です。具体例として、 GitHub CLI などでコンテナ内に認証済みの状態を作る場合は、その権限も AI が利用できる前提になるため、用途を限定したトークンを使う、必要最小限の権限に絞る、といった配慮が必要です。これによって、LLM がプロンプトインジェクション等を受けて暴走した場合でも、影響範囲をかなり限定できます。