2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Dev Container入門 - Dockerを使用して開発環境を統一する

Last updated at Posted at 2025-07-14

はじめに

「新メンバーの環境構築に3日かかった」「私の環境では動くのに...」こんな経験はありませんか?

Dev Container(Development Container)は、これらの問題を根本的に解決する技術です。本記事では、Dev Containerの基本から実践的な活用法まで、実例を交えて詳しく解説します。

Dev Containerとは?

Dev Containerは、VS Codeと組み合わせて使用する開発環境コンテナ化技術です。プロジェクトの開発環境をDockerコンテナとして定義し、チーム全体で同じ環境を共有できます。

主な特徴

  • 環境の統一: チーム全員が同じ開発環境を使用
  • 即座のセットアップ: 新規参加者も数分で開発開始可能
  • 隔離された環境: ホストマシンを汚さず、プロジェクトごとに独立
  • 拡張機能の共有: VS Code拡張機能も含めて環境を統一

Windows開発環境の比較

Windows環境で開発する際の選択肢として、Windowsローカル、WSL2、Dev Containerがあります。それぞれの特徴を比較してみましょう。

チーム開発での課題

項目 Windowsローカル WSL2 Dev Container
実行環境 Windows Native Linux on Hyper-V コンテナ化Linux
"私の環境では動く" ❌ 頻発 ⚠️ 時々発生 ✅ 発生しない
新メンバー参加時間 🔴 数時間〜数日 🟡 数時間 💚 数分
環境構築ドキュメント 📚 膨大 📖 必要 📄 最小限
WindowsとMacの差異 ❌ 多発 ⚠️ 軽減 ✅ なし

具体的な問題例

Windowsローカル開発でよくある問題

# パス区切り文字の違い
Windows: C:\project\src\index.js
Linux:   /project/src/index.js

# 改行コードの違い
Windows: CRLF (\r\n)
Linux:   LF (\n)

# 環境変数の設定
Windows: set NODE_ENV=production
macOS:   export NODE_ENV=production

WindowsとMacOS間の開発環境差異

# Docker Desktop の違い
Windows: WSL2 バックエンド、メモリ制限設定が必要
macOS:   ネイティブ仮想化、Rosetta 2(Apple Silicon)

# パッケージマネージャー
Windows: Chocolatey/Scoop
macOS:   Homebrew

開発環境選択の指針

❌ Windowsローカルが問題となるケース

  • チーム開発: 環境の統一が困難
  • Linux系本番環境: 動作の差異が発生
  • 複数プロジェクト: 依存関係の競合

✅ Dev Containerが解決する問題

  • 完全な環境分離: プロジェクト間の依存関係競合なし
  • 即座のセットアップ: 新メンバーも数分で開発開始
  • OS差異の解消: WindowsもMacも同一のLinux環境

セットアップ方法

前提条件

  • VS Code
  • Docker Desktop
  • Dev Containers拡張機能

Dev Containers拡張機能の使い方

VS CodeでDev Containerを使用するには、「Dev Containers」拡張機能(旧称:Remote - Containers)をインストールする必要があります。

拡張機能のインストール:

  1. VS Codeの拡張機能タブ(左サイドバーの四角いアイコン)を開く
  2. 「Dev Containers」で検索
  3. Microsoft製の「Dev Containers」をインストール

Dev Containerの起動方法:

  1. コマンドパレットから起動

    • F1またはCtrl+Shift+P(Mac: Cmd+Shift+P)でコマンドパレットを開く
    • 「Dev Containers: Reopen in Container」を選択
  2. 左下のリモート接続アイコンから起動

    • VS Code左下の緑色の><アイコンをクリック
    • 「Reopen in Container」を選択
  3. 通知から起動

    • .devcontainerフォルダがあるプロジェクトを開くと、右下に通知が表示
    • 「Reopen in Container」ボタンをクリック

接続状態の確認:

  • 左下の><アイコンが「Dev Container: [環境名]」と表示されていれば接続中
  • 例:>< Dev Container: フルスタック Node.js 開発環境

ローカル環境に戻る:

  • 左下の「Dev Container: [環境名]」をクリック
  • 「Reopen Folder Locally」を選択

基本的なファイル構成

プロジェクトルートに.devcontainerフォルダを作成し、以下のファイルを配置します:

project-root/
├── .devcontainer/
│   ├── devcontainer.json
│   └── Dockerfile
└── src/

実践例1:フロントエンド開発環境(Next.js + TypeScript)

モダンなフロントエンド開発に必要な全ての要素を含む環境を構築します。

プロジェクト構成

frontend-app/
├── .devcontainer/
│   └── devcontainer.json
├── src/
│   ├── app/                 # App Router
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   └── globals.css
│   ├── components/
│   │   ├── ui/            # UIコンポーネント
│   │   └── features/      # 機能別コンポーネント
│   ├── hooks/             # カスタムフック
│   ├── lib/               # ユーティリティ
│   └── types/             # 型定義
├── public/
├── .eslintrc.json
├── .prettierrc
├── tailwind.config.ts
├── next.config.js
├── package.json
└── tsconfig.json

devcontainer.json

{
  "name": "フロントエンド開発環境",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:18",
  
  "customizations": {
    "vscode": {
      "extensions": [
        // TypeScript/JavaScript
        "ms-vscode.vscode-typescript-next",
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        
        // React開発
        "dsznajder.es7-react-js-snippets",
        "burkeholland.simple-react-snippets",
        
        // CSS/Tailwind
        "bradlc.vscode-tailwindcss",
        "stylelint.vscode-stylelint",
        
        // 開発効率化
        "christian-kohler.path-intellisense",
        "formulahendry.auto-rename-tag",
        "pranaygp.vscode-css-peek",
        
        // テスト
        "orta.vscode-jest",
        "ms-playwright.playwright",
        
        // その他
        "yoavbls.pretty-ts-errors",
        "usernamehw.errorlens"
      ],
      
      "settings": {
        // エディタ設定
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.codeActionsOnSave": {
          "source.fixAll.eslint": true,
          "source.organizeImports": true
        },
        
        // TypeScript設定
        "typescript.tsdk": "node_modules/typescript/lib",
        "typescript.enablePromptUseWorkspaceTsdk": true,
        "typescript.preferences.importModuleSpecifier": "relative",
        
        // Tailwind CSS
        "tailwindCSS.experimental.classRegex": [
          ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
          ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
        ],
        
        // ファイル関連
        "files.exclude": {
          "**/.next": true,
          "**/node_modules": true
        }
      }
    }
  },
  
  "features": {
    "ghcr.io/devcontainers/features/common-utils:2": {
      "installZsh": true
    }
  },
  
  "postCreateCommand": "npm install && npm run prepare",
  
  "forwardPorts": [3000, 9229],
  
  "portsAttributes": {
    "3000": {
      "label": "Next.js Dev Server",
      "onAutoForward": "openPreview"
    },
    "9229": {
      "label": "Node.js Debugger",
      "onAutoForward": "silent"
    }
  },
  
  "containerEnv": {
    "NEXT_TELEMETRY_DISABLED": "1"
  }
}

実践例2:バックエンド開発環境(Node.js + Express + Prisma)

本格的なREST API/GraphQL開発に対応したバックエンド環境を構築します。

プロジェクト構成

backend-api/
├── .devcontainer/
│   ├── devcontainer.json
│   └── docker-compose.yml
├── src/
│   ├── controllers/       # APIエンドポイント
│   ├── services/         # ビジネスロジック
│   ├── repositories/     # データアクセス層
│   ├── middlewares/      # Express ミドルウェア
│   ├── utils/           # ユーティリティ
│   ├── types/           # 型定義
│   ├── config/          # 設定ファイル
│   └── app.ts           # Expressアプリケーション
├── prisma/
│   ├── schema.prisma    # データベーススキーマ
│   └── migrations/      # マイグレーション
├── tests/
│   ├── unit/           # ユニットテスト
│   └── integration/    # 統合テスト
├── .env.example
├── jest.config.js
├── tsconfig.json
└── package.json

devcontainer.json

{
  "name": "バックエンド開発環境",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  
  "customizations": {
    "vscode": {
      "extensions": [
        // TypeScript/JavaScript
        "ms-vscode.vscode-typescript-next",
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        
        // データベース関連
        "prisma.prisma",
        "cweijan.vscode-postgresql-client2",
        
        // API開発
        "humao.rest-client",
        "GraphQL.vscode-graphql",
        "GraphQL.vscode-graphql-syntax",
        
        // テスト
        "orta.vscode-jest",
        "firsttris.vscode-jest-runner",
        
        // デバッグ
        "ms-vscode.js-debug-nightly",
        
        // その他
        "mikestead.dotenv",
        "usernamehw.errorlens",
        "aaron-bond.better-comments"
      ],
      
      "settings": {
        // エディタ設定
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "[prisma]": {
          "editor.defaultFormatter": "Prisma.prisma"
        },
        
        // TypeScript設定
        "typescript.tsdk": "node_modules/typescript/lib",
        "typescript.enablePromptUseWorkspaceTsdk": true,
        
        // デバッグ設定
        "debug.javascript.autoAttachFilter": "smart",
        
        // Prisma設定
        "prisma.showPrismaDataPlatformNotification": false,
        
        // Jest設定
        "jest.autoRun": {
          "watch": false,
          "onSave": "test-file"
        }
      }
    }
  },
  
  "features": {
    "ghcr.io/devcontainers/features/common-utils:2": {
      "installZsh": true
    },
    "ghcr.io/devcontainers/features/git:1": {}
  },
  
  "postCreateCommand": "npm install && npm run db:push && npm run db:seed",
  
  "forwardPorts": [3001, 5432, 6379],
  
  "portsAttributes": {
    "3001": {
      "label": "Express API",
      "onAutoForward": "notify"
    },
    "5432": {
      "label": "PostgreSQL",
      "onAutoForward": "silent"
    },
    "6379": {
      "label": "Redis",
      "onAutoForward": "silent"
    }
  },
  
  "containerEnv": {
    "NODE_ENV": "development",
    "DATABASE_URL": "postgresql://postgres:password@db:5432/apidb",
    "REDIS_URL": "redis://cache:6379"
  }
}

docker-compose.yml

version: '3.8'

services:
  app:
    image: mcr.microsoft.com/devcontainers/typescript-node:18
    volumes:
      - ..:/workspace:cached
      - node_modules:/workspace/node_modules
    command: sleep infinity
    environment:
      DATABASE_URL: postgresql://postgres:password@db:5432/apidb
      REDIS_URL: redis://cache:6379
      JWT_SECRET: development-secret-key
    depends_on:
      - db
      - cache

  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: apidb
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  cache:
    image: redis:7-alpine
    restart: unless-stopped
    ports:
      - "6379:6379"

volumes:
  postgres-data:
  node_modules:

実践例3:フルスタックNode.js開発環境

この例では、以下の構成のWebアプリケーション開発環境を構築します:

  • フロントエンド: React + TypeScript + Vite
  • バックエンド: Node.js + Express + TypeScript
  • データベース: PostgreSQL
  • キャッシュ: Redis
  • 開発ツール: ESLint, Prettier, Jest

devcontainer.json

{
  // 開発環境の名前(VS Codeのステータスバーに表示)
  "name": "フルスタック Node.js 開発環境",
  
  // Docker Composeを使用して複数のサービスを起動
  "dockerComposeFile": "../docker-compose.dev.yml",
  "service": "app",
  "workspaceFolder": "/workspace",

  // VS Code関連の設定
  "customizations": {
    "vscode": {
      // 自動インストールする拡張機能
      "extensions": [
        // TypeScript開発
        "ms-vscode.vscode-typescript-next",
        
        // コードフォーマット・品質
        "esbenp.prettier-vscode",
        "dbaeumer.vscode-eslint",
        
        // React開発支援
        "dsznajder.es7-react-js-snippets",
        "bradlc.vscode-tailwindcss",
        
        // データベース管理
        "cweijan.vscode-postgresql-client2",
        "cweijan.vscode-redis-client",
        
        // API開発・テスト
        "humao.rest-client",
        "ms-vscode.vscode-json",
        
        // Git関連
        "eamodio.gitlens",
        
        // Docker関連
        "ms-azuretools.vscode-docker",
        
        // テスト関連
        "orta.vscode-jest"
      ],
      
      // エディタ設定
      "settings": {
        // デフォルトターミナル
        "terminal.integrated.defaultProfile.linux": "bash",
        
        // 自動フォーマット設定
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.codeActionsOnSave": {
          "source.fixAll.eslint": true
        },
        
        // TypeScript設定
        "typescript.preferences.importModuleSpecifier": "relative",
        "typescript.suggest.autoImports": true,
        
        // ファイル監視設定(パフォーマンス向上)
        "files.watcherExclude": {
          "**/node_modules/**": true,
          "**/dist/**": true,
          "**/build/**": true
        },
        
        // ESLint設定
        "eslint.workingDirectories": ["frontend", "backend"],
        
        // データベース接続設定(開発用)
        "vscode-postgresql-client2.database": {
          "host": "db",
          "port": 5432,
          "user": "postgres",
          "password": "password",
          "database": "devdb"
        }
      }
    }
  },

  // ポートフォワーディング設定
  "forwardPorts": [
    3000,  // フロントエンド開発サーバー (Vite)
    3001,  // バックエンドAPI サーバー (Express)
    5432,  // PostgreSQL
    6379   // Redis
  ],
  
  // ポートの詳細設定
  "portsAttributes": {
    "3000": {
      "label": "フロントエンド (React)",
      "onAutoForward": "openBrowser"
    },
    "3001": {
      "label": "バックエンド API",
      "onAutoForward": "notify"
    },
    "5432": {
      "label": "PostgreSQL",
      "onAutoForward": "silent"
    },
    "6379": {
      "label": "Redis",
      "onAutoForward": "silent"
    }
  },

  // コンテナ作成後に実行するコマンド
  "postCreateCommand": "npm run setup:dev",
  
  // 開発用の環境変数
  "containerEnv": {
    "NODE_ENV": "development",
    "DATABASE_URL": "postgresql://postgres:password@db:5432/devdb",
    "REDIS_URL": "redis://redis:6379",
    "API_BASE_URL": "http://localhost:3001"
  },

  // コンテナ内での実行ユーザー
  "remoteUser": "node"
}

docker-compose.dev.yml

version: '3.8'

services:
  # メインの開発環境コンテナ
  app:
    build:
      context: .
      dockerfile: .devcontainer/Dockerfile
    volumes:
      # ソースコードをマウント
      - ..:/workspace:cached
      # node_modulesのボリューム(パフォーマンス向上)
      - node_modules_frontend:/workspace/frontend/node_modules
      - node_modules_backend:/workspace/backend/node_modules
    # コンテナを常時起動状態に保つ
    command: sleep infinity
    depends_on:
      - db
      - redis
    environment:
      - NODE_ENV=development
    networks:
      - dev-network

  # PostgreSQL データベース
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: devdb
      # 日本語対応
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=ja_JP.UTF-8"
    volumes:
      # データ永続化
      - postgres-data:/var/lib/postgresql/data
      # 初期化スクリプト
      - ./.devcontainer/init-db:/docker-entrypoint-initdb.d
    ports:
      - "5432:5432"
    networks:
      - dev-network

  # Redis キャッシュサーバー
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"
    networks:
      - dev-network

  # pgAdmin (データベース管理ツール)
  pgadmin:
    image: dpage/pgadmin4:latest
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: admin
    ports:
      - "8080:80"
    depends_on:
      - db
    networks:
      - dev-network

volumes:
  postgres-data:
  redis-data:
  node_modules_frontend:
  node_modules_backend:

networks:
  dev-network:
    driver: bridge

Dockerfile

# Node.js 18 LTS with Alpine Linux (軽量)
FROM node:18-alpine

# 必要なシステムパッケージのインストール
RUN apk add --no-cache \
    git \
    curl \
    bash \
    vim \
    postgresql-client \
    redis \
    # Python関連(一部のnpmパッケージで必要)
    python3 \
    make \
    g++

# pnpm のインストール(高速なパッケージマネージャー)
RUN npm install -g pnpm@latest

# 開発用グローバルツールのインストール
RUN pnpm add -g \
    typescript \
    ts-node \
    nodemon \
    concurrently \
    cross-env \
    rimraf

# 作業ディレクトリの設定
WORKDIR /workspace

# nodeユーザーに権限を付与
RUN chown -R node:node /workspace

# nodeユーザーに切り替え(セキュリティ向上)
USER node

# シェル設定(開発体験向上)
RUN echo 'alias ll="ls -la"' >> ~/.bashrc
RUN echo 'alias la="ls -la"' >> ~/.bashrc
RUN echo 'alias ..="cd .."' >> ~/.bashrc

package.json(ルート)

{
  "name": "fullstack-nodejs-app",
  "private": true,
  "workspaces": [
    "frontend",
    "backend"
  ],
  "scripts": {
    "setup:dev": "pnpm install && pnpm run db:setup && pnpm run build",
    "dev": "concurrently \"pnpm run dev:backend\" \"pnpm run dev:frontend\"",
    "dev:frontend": "pnpm --filter frontend dev",
    "dev:backend": "pnpm --filter backend dev",
    "build": "pnpm run build:frontend && pnpm run build:backend",
    "build:frontend": "pnpm --filter frontend build",
    "build:backend": "pnpm --filter backend build",
    "test": "pnpm run test:frontend && pnpm run test:backend",
    "test:frontend": "pnpm --filter frontend test",
    "test:backend": "pnpm --filter backend test",
    "lint": "pnpm run lint:frontend && pnpm run lint:backend",
    "lint:frontend": "pnpm --filter frontend lint",
    "lint:backend": "pnpm --filter backend lint",
    "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
    "db:setup": "pnpm --filter backend db:migrate && pnpm --filter backend db:seed"
  },
  "devDependencies": {
    "concurrently": "^8.2.0",
    "prettier": "^3.0.0"
  }
}

プロジェクト構成

project-root/
├── .devcontainer/
│   ├── devcontainer.json
│   ├── Dockerfile
│   └── init-db/
│       └── 01-init.sql
├── docker-compose.dev.yml
├── package.json
├── frontend/                    # React + TypeScript
│   ├── src/
│   ├── package.json
│   ├── vite.config.ts
│   └── tsconfig.json
├── backend/                     # Express + TypeScript
│   ├── src/
│   ├── package.json
│   ├── tsconfig.json
│   └── prisma/
└── shared/                      # 共通の型定義など
    └── types/

開発フロー

  1. コンテナ起動: VS Codeで「Dev Containerで再度開く」
  2. 依存関係インストール: 自動実行(postCreateCommand)
  3. データベース初期化: 自動実行
  4. 開発サーバー起動: pnpm run dev
  5. ブラウザアクセス:

実践例4:Java 21 + Spring Boot 3.x + PostgreSQL + Redis + Next.js フルスタック開発環境

モダンなJava/TypeScriptフルスタック開発環境を構築します。Java 21 + Spring Boot 3.x + PostgreSQL + Redis + Next.jsの組み合わせで、エンタープライズレベルのWebアプリケーション開発に対応します。

プロジェクト構成

fullstack-springboot-app/
├── .devcontainer/
│   ├── devcontainer.json
│   ├── docker-compose.yml
│   └── Dockerfile
├── backend/                # Spring Boot Application
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/
│   │   │   │   └── com/example/api/
│   │   │   │       ├── ApiApplication.java
│   │   │   │       ├── controller/
│   │   │   │       ├── service/
│   │   │   │       ├── repository/
│   │   │   │       ├── entity/
│   │   │   │       └── config/
│   │   │   └── resources/
│   │   │       ├── application.yml
│   │   │       └── data.sql
│   │   └── test/
│   ├── build.gradle
│   └── gradle/
├── frontend/               # Next.js Application
│   ├── src/
│   │   ├── app/           # App Router
│   │   ├── components/
│   │   ├── lib/
│   │   └── types/
│   ├── public/
│   ├── package.json
│   ├── next.config.js
│   └── tailwind.config.ts
└── shared/                 # 共通設定
    └── types/

devcontainer.json

{
  "name": "Spring Boot + Next.js フルスタック開発環境",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  
  "customizations": {
    "vscode": {
      "extensions": [
        // Java開発
        "redhat.java",
        "vscjava.vscode-java-pack",
        "vscjava.vscode-spring-boot",
        "vscjava.vscode-spring-initializr",
        "vscjava.vscode-spring-boot-dashboard",
        "pivotal.vscode-boot-dev-pack",
        
        // TypeScript/JavaScript
        "ms-vscode.vscode-typescript-next",
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        
        // React/Next.js
        "dsznajder.es7-react-js-snippets",
        "bradlc.vscode-tailwindcss",
        
        // データベース
        "cweijan.vscode-postgresql-client2",
        "cweijan.vscode-redis-client",
        "mtxr.sqltools",
        "mtxr.sqltools-driver-pg",
        
        // API開発・テスト
        "humao.rest-client",
        "42crunch.vscode-openapi",
        
        // Build Tools
        "vscjava.vscode-gradle",
        "richardwillis.vscode-gradle",
        
        // その他
        "editorconfig.editorconfig",
        "mikestead.dotenv",
        "usernamehw.errorlens"
      ],
      
      "settings": {
        // Java設定
        "java.configuration.runtimes": [
          {
            "name": "JavaSE-21",
            "path": "/usr/lib/jvm/java-21-openjdk",
            "default": true
          }
        ],
        "java.compile.nullAnalysis.mode": "automatic",
        "java.format.settings.url": "https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml",
        
        // Spring Boot設定
        "spring-boot.ls.java.home": "/usr/lib/jvm/java-21-openjdk",
        "spring.initializr.defaultLanguage": "Java",
        "spring.initializr.defaultJavaVersion": "21",
        "spring.initializr.defaultPackaging": "Jar",
        
        // TypeScript/JavaScript設定
        "typescript.tsdk": "frontend/node_modules/typescript/lib",
        "typescript.preferences.importModuleSpecifier": "relative",
        
        // エディタ設定
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "[java]": {
          "editor.defaultFormatter": "redhat.java",
          "editor.tabSize": 4
        },
        "[typescript]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[javascript]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        
        // ファイル関連
        "files.exclude": {
          "**/node_modules": true,
          "**/build": true,
          "**/target": true,
          "**/.gradle": true,
          "**/.next": true
        }
      }
    }
  },
  
  "features": {
    "ghcr.io/devcontainers/features/java:1": {
      "version": "21",
      "jdkDistro": "tem"
    },
    "ghcr.io/devcontainers/features/gradle:1": {
      "version": "8.5"
    },
    "ghcr.io/devcontainers/features/node:1": {
      "version": "20"
    },
    "ghcr.io/devcontainers/features/common-utils:2": {
      "installZsh": true
    }
  },
  
  "forwardPorts": [8080, 3000, 5432, 6379, 8081],
  
  "portsAttributes": {
    "8080": {
      "label": "Spring Boot API",
      "onAutoForward": "notify"
    },
    "3000": {
      "label": "Next.js Frontend",
      "onAutoForward": "openBrowser"
    },
    "5432": {
      "label": "PostgreSQL",
      "onAutoForward": "silent"
    },
    "6379": {
      "label": "Redis",
      "onAutoForward": "silent"
    },
    "8081": {
      "label": "pgAdmin",
      "onAutoForward": "notify"
    }
  },
  
  "postCreateCommand": "cd frontend && npm install && cd ../backend && ./gradlew build",
  
  "containerEnv": {
    "SPRING_PROFILES_ACTIVE": "development",
    "SPRING_DATASOURCE_URL": "jdbc:postgresql://db:5432/springbootdb",
    "SPRING_DATASOURCE_USERNAME": "springuser",
    "SPRING_DATASOURCE_PASSWORD": "springpass",
    "SPRING_REDIS_HOST": "redis",
    "SPRING_REDIS_PORT": "6379",
    "NEXT_PUBLIC_API_URL": "http://localhost:8080"
  }
}

docker-compose.yml

version: '3.8'

services:
  # 開発環境コンテナ
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ..:/workspace:cached
      - gradle-cache:/home/vscode/.gradle
      - node_modules_cache:/workspace/frontend/node_modules
    command: sleep infinity
    depends_on:
      - db
      - redis
    environment:
      - SPRING_PROFILES_ACTIVE=development
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/springbootdb
      - SPRING_DATASOURCE_USERNAME=springuser
      - SPRING_DATASOURCE_PASSWORD=springpass
      - SPRING_REDIS_HOST=redis
      - SPRING_REDIS_PORT=6379
      - NEXT_PUBLIC_API_URL=http://localhost:8080
    networks:
      - dev-network

  # PostgreSQL データベース
  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: springbootdb
      POSTGRES_USER: springuser
      POSTGRES_PASSWORD: springpass
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=ja_JP.UTF-8"
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./init-db:/docker-entrypoint-initdb.d
    ports:
      - "5432:5432"
    networks:
      - dev-network

  # Redis キャッシュサーバー
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass "redispassword"
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"
    networks:
      - dev-network

  # pgAdmin (PostgreSQL管理ツール)
  pgadmin:
    image: dpage/pgadmin4:latest
    restart: unless-stopped
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: admin
      PGADMIN_CONFIG_SERVER_MODE: "False"
    volumes:
      - pgadmin-data:/var/lib/pgadmin
    ports:
      - "8081:80"
    depends_on:
      - db
    networks:
      - dev-network

  # Redis Commander (Redis管理ツール)
  redis-commander:
    image: rediscommander/redis-commander:latest
    restart: unless-stopped
    environment:
      REDIS_HOSTS: "local:redis:6379:0:redispassword"
    ports:
      - "8082:8081"
    depends_on:
      - redis
    networks:
      - dev-network

volumes:
  postgres-data:
  redis-data:
  pgadmin-data:
  gradle-cache:
  node_modules_cache:

networks:
  dev-network:
    driver: bridge

Dockerfile

FROM mcr.microsoft.com/devcontainers/base:ubuntu

# システムパッケージの更新
RUN apt-get update && apt-get install -y \
    wget \
    curl \
    git \
    vim \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Java 21のインストール
RUN apt-get update && apt-get install -y openjdk-21-jdk

# Node.js 20のインストール
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs

# Gradleのインストール
RUN wget https://services.gradle.org/distributions/gradle-8.5-bin.zip -P /tmp \
    && unzip /tmp/gradle-8.5-bin.zip -d /opt \
    && ln -s /opt/gradle-8.5/bin/gradle /usr/local/bin/gradle

# PostgreSQL ClientとRedis Clientのインストール
RUN apt-get update && apt-get install -y \
    postgresql-client \
    redis-tools \
    && rm -rf /var/lib/apt/lists/*

# 作業ディレクトリの設定
WORKDIR /workspace

# 権限設定
RUN chown -R vscode:vscode /workspace
USER vscode

# 環境変数の設定
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
ENV PATH=$PATH:$JAVA_HOME/bin
ENV GRADLE_HOME=/opt/gradle-8.5
ENV PATH=$PATH:$GRADLE_HOME/bin

backend/application.yml

spring:
  profiles:
    active: development
  
  datasource:
    url: jdbc:postgresql://db:5432/springbootdb
    username: springuser
    password: springpass
    driver-class-name: org.postgresql.Driver
    
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.PostgreSQLDialect
  
  redis:
    host: redis
    port: 6379
    password: redispassword
    timeout: 3000ms
    
  cache:
    type: redis
    redis:
      time-to-live: 3600000
      
  web:
    cors:
      allowed-origins: http://localhost:3000
      allowed-methods: GET,POST,PUT,DELETE,OPTIONS
      allowed-headers: "*"
      allow-credentials: true

server:
  port: 8080
  servlet:
    context-path: /api

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always

logging:
  level:
    com.example.api: DEBUG
    org.springframework.web: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE

backend/build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '21'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // Spring Boot
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    
    // Database
    implementation 'org.postgresql:postgresql'
    implementation 'redis.clients:jedis'
    
    // JSON Web Token
    implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
    
    // OpenAPI Documentation
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
    
    // Lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    
    // Test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'org.testcontainers:junit-jupiter'
    testImplementation 'org.testcontainers:postgresql'
}

tasks.named('test') {
    useJUnitPlatform()
}

frontend/package.json

{
  "name": "springboot-nextjs-frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "type-check": "tsc --noEmit"
  },
  "dependencies": {
    "next": "14.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "axios": "^1.6.0",
    "swr": "^2.2.0",
    "react-query": "^3.39.0",
    "react-hook-form": "^7.47.0",
    "zod": "^3.22.0",
    "@hookform/resolvers": "^3.3.0",
    "clsx": "^2.0.0",
    "tailwindcss": "^3.3.0"
  },
  "devDependencies": {
    "@types/node": "^20.8.0",
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "autoprefixer": "^10.4.0",
    "eslint": "^8.51.0",
    "eslint-config-next": "14.0.0",
    "postcss": "^8.4.0",
    "typescript": "^5.2.0"
  }
}

開発フロー

  1. 環境起動: VS Codeで「Dev Containerで再度開く」を選択
  2. 依存関係インストール: postCreateCommandで自動実行
  3. バックエンド起動: cd backend && ./gradlew bootRun
  4. フロントエンド起動: cd frontend && npm run dev
  5. アクセス確認:

この環境では、Java 21 + Spring Boot 3.x + PostgreSQL + Redis + Next.js 14の最新構成を使用し、エンタープライズレベルのWebアプリケーション開発に最適化されています。

実践例5:レガシーPHP 5.3 + CakePHP開発環境

古いCakePHP(1.3/2.x)システムの保守・移行作業のための環境を構築します。

プロジェクト構成

cakephp-legacy-app/
├── .devcontainer/
│   ├── devcontainer.json
│   ├── docker-compose.yml
│   └── Dockerfile
├── app/                    # CakePHPアプリケーション
│   ├── Config/
│   ├── Controller/
│   ├── Model/
│   ├── View/
│   ├── webroot/          # DocumentRoot
│   ├── Plugin/
│   └── tmp/              # キャッシュ・ログ
├── lib/                   # CakePHPコアライブラリ
│   └── Cake/
├── vendors/               # サードパーティライブラリ
└── logs/

devcontainer.json

{
  "name": "PHP 5.3 + CakePHP レガシー環境",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  
  "customizations": {
    "vscode": {
      "extensions": [
        // PHP開発
        "bmewburn.vscode-intelephense-client",
        "felixfbecker.php-debug",
        "neilbrayfield.php-docblocker",
        
        // CakePHP専用
        "janhartigan.cakephp3-snippets",
        
        // データベース
        "mtxr.sqltools",
        "mtxr.sqltools-driver-mysql",
        
        // その他
        "editorconfig.editorconfig",
        "mikestead.dotenv"
      ],
      
      "settings": {
        // PHP設定
        "php.suggest.basic": false,
        "php.validate.enable": true,
        "php.validate.executablePath": "/usr/local/bin/php",
        
        // Intelephense設定(PHP 5.3対応)
        "intelephense.environment.phpVersion": "5.3.29",
        "intelephense.environment.includePaths": [
          "/workspace/app",
          "/workspace/lib/Cake"
        ],
        
        // CakePHP関連
        "files.associations": {
          "*.ctp": "php",
          "*.php": "php"
        },
        
        // エディタ設定
        "editor.tabSize": 4,
        "editor.insertSpaces": false,  // CakePHPはタブを使用
        "[php]": {
          "editor.defaultFormatter": "bmewburn.vscode-intelephense-client"
        }
      }
    }
  },
  
  "forwardPorts": [80, 3306, 8080],
  
  "portsAttributes": {
    "80": {
      "label": "Apache",
      "onAutoForward": "openBrowser"
    },
    "3306": {
      "label": "MySQL 5.5",
      "onAutoForward": "silent"
    },
    "8080": {
      "label": "phpMyAdmin",
      "onAutoForward": "notify"
    }
  },
  
  "postCreateCommand": "chmod -R 777 /workspace/app/tmp && apache2ctl start"
}

docker-compose.yml

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ..:/workspace:cached
      - ./php.ini:/usr/local/etc/php/php.ini
    ports:
      - "80:80"
    environment:
      - APACHE_DOCUMENT_ROOT=/workspace/app/webroot
      - PHP_DISPLAY_ERRORS=1
      - PHP_ERROR_REPORTING=E_ALL & ~E_DEPRECATED & ~E_STRICT
    depends_on:
      - db

  db:
    image: mysql:5.5  # PHP 5.3時代の標準的なMySQLバージョン
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: cakephp_legacy
      MYSQL_USER: cakephp
      MYSQL_PASSWORD: cakephp_password
    volumes:
      - mysql-data:/var/lib/mysql
      - ./mysql-init:/docker-entrypoint-initdb.d
    ports:
      - "3306:3306"
    command: >
      --character-set-server=utf8
      --collation-server=utf8_general_ci
      --innodb_file_per_table=1
      --innodb_file_format=Barracuda
      --max_allowed_packet=16M

  phpmyadmin:
    image: phpmyadmin/phpmyadmin:4.9  # PHP 5対応の最終バージョン
    restart: unless-stopped
    environment:
      PMA_HOST: db
      PMA_USER: root
      PMA_PASSWORD: root
      PHP_MAX_INPUT_VARS: 1000
    ports:
      - "8080:80"
    depends_on:
      - db

volumes:
  mysql-data:

app/Config/database.php(CakePHP設定例)

<?php
class DATABASE_CONFIG {
    public $default = array(
        'datasource' => 'Database/Mysql',
        'persistent' => false,
        'host' => 'db',
        'login' => 'cakephp',
        'password' => 'cakephp_password',
        'database' => 'cakephp_legacy',
        'prefix' => '',
        'encoding' => 'utf8',
    );
}

この環境では、PHP 5.3時代の標準的な構成(MySQL 5.5)を使用し、CakePHPの開発に最適化された設定になっています。

よく使用する設定オプション

ポートフォワーディング

{
  "forwardPorts": [3000, 8080],
  "portsAttributes": {
    "3000": {
      "label": "フロントエンド",
      "onAutoForward": "notify"
    },
    "8080": {
      "label": "API サーバー",
      "onAutoForward": "openBrowser"
    }
  }
}

環境変数の設定

{
  "containerEnv": {
    "NODE_ENV": "development",
    "API_URL": "http://localhost:8080"
  },
  "remoteEnv": {
    "PATH": "${containerEnv:PATH}:/custom/bin"
  }
}

マウントの設定

{
  "mounts": [
    "source=${localWorkspaceFolder}/data,target=/workspace/data,type=bind",
    "source=node_modules_cache,target=/workspace/node_modules,type=volume"
  ]
}

実用的なTips

1. 開発用ツールの追加

# Dockerfile内で開発ツールをインストール
RUN apt-get update && apt-get install -y \
    fish \
    htop \
    tree \
    jq \
    && rm -rf /var/lib/apt/lists/*

# 開発用の便利なツール
RUN npm install -g nodemon typescript ts-node

2. Git設定の引き継ぎ

{
  "mounts": [
    "source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,consistency=cached"
  ]
}

3. SSH認証の設定

{
  "features": {
    "ghcr.io/devcontainers/features/common-utils:2": {
      "installZsh": true,
      "configureZshAsDefaultShell": true
    },
    "ghcr.io/devcontainers/features/git:1": {},
    "ghcr.io/devcontainers/features/github-cli:1": {}
  },
  "mounts": [
    "source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached"
  ]
}

トラブルシューティング

よくある問題と解決法

問題1: コンテナの起動が遅い

M1/M2 Macなど、ARM系のプロセッサを使用している場合、Dockerイメージのアーキテクチャが異なるため起動が遅くなることがあります。明示的にプラットフォームを指定することで、適切なイメージを使用し、起動時間を短縮できます。

{
  "build": {
    "dockerfile": "Dockerfile",
    "options": ["--platform=linux/amd64"]
  }
}

問題2: ファイル変更の検知が遅い

WindowsやmacOSでDockerを使用する場合、ホストとコンテナ間のファイル同期が遅いことがあります。特に大量のファイルを含むプロジェクト(node_modulesなど)では顕著です。consistency=cachedオプションを使用することで、パフォーマンスを向上させることができます。

{
  "mounts": [
    "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"
  ]
}

問題3: 権限の問題

コンテナ内でファイルを作成・編集する際、権限エラーが発生することがあります。これは、コンテナ内のユーザーIDとホストのユーザーIDが異なるために起こります。Dockerfile内で適切なユーザーを作成し、そのユーザーで実行することで解決できます。

# Dockerfile内で適切なユーザー設定
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# グループとユーザーを作成
RUN groupadd --gid $USER_GID $USERNAME \
    && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
    && apt-get update \
    && apt-get install -y sudo \
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME

# 作成したユーザーに切り替え
USER $USERNAME

チーム導入のベストプラクティス

1. 段階的な導入

  1. 試験導入: 小さなプロジェクトから開始
  2. 設定の標準化: チーム共通の設定テンプレートを作成
  3. ドキュメント整備: セットアップ手順と troubleshooting ガイドを作成

2. 設定管理

// .devcontainer/devcontainer.json
{
  "name": "Company Standard Dev Environment",
  "image": "your-company/dev-base:latest",
  "features": {
    "ghcr.io/your-company/features/common-tools:1": {}
  }
}

3. CI/CDとの連携

# .github/workflows/test.yml
name: Test in Dev Container
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: devcontainers/ci@v0.3
        with:
          runCmd: npm test

まとめ

Dev Containerを導入することで、チーム開発における多くの課題を解決できます。

主なメリット:

  • 環境構築時間の劇的短縮(数時間→数分)
  • 「動かない」問題の削減
  • 新メンバーのオンボーディング改善
  • プロジェクト間の環境分離

導入のポイント:

  • 小さく始めて段階的に拡張
  • チーム共通の設定テンプレート作成
  • 継続的な改善とメンテナンス

Dev Containerは単なる技術ツールではなく、チーム開発の生産性を根本的に向上させる投資です。ぜひプロジェクトに導入して、その効果を実感してください。

参考リンク

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?