はじめに
「新メンバーの環境構築に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)をインストールする必要があります。
拡張機能のインストール:
- VS Codeの拡張機能タブ(左サイドバーの四角いアイコン)を開く
- 「Dev Containers」で検索
- Microsoft製の「Dev Containers」をインストール
Dev Containerの起動方法:
-
コマンドパレットから起動
-
F1
またはCtrl+Shift+P
(Mac:Cmd+Shift+P
)でコマンドパレットを開く - 「Dev Containers: Reopen in Container」を選択
-
-
左下のリモート接続アイコンから起動
- VS Code左下の緑色の
><
アイコンをクリック - 「Reopen in Container」を選択
- VS Code左下の緑色の
-
通知から起動
-
.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/
開発フロー
- コンテナ起動: VS Codeで「Dev Containerで再度開く」
- 依存関係インストール: 自動実行(postCreateCommand)
- データベース初期化: 自動実行
-
開発サーバー起動:
pnpm run dev
-
ブラウザアクセス:
- フロントエンド: http://localhost:3000
- バックエンドAPI: http://localhost:3001
- pgAdmin: http://localhost:8080
実践例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"
}
}
開発フロー
- 環境起動: VS Codeで「Dev Containerで再度開く」を選択
-
依存関係インストール:
postCreateCommand
で自動実行 -
バックエンド起動:
cd backend && ./gradlew bootRun
-
フロントエンド起動:
cd frontend && npm run dev
-
アクセス確認:
- フロントエンド: http://localhost:3000
- バックエンドAPI: http://localhost:8080/api
- Swagger UI: http://localhost:8080/api/swagger-ui.html
- pgAdmin: http://localhost:8081
- Redis Commander: http://localhost:8082
この環境では、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. 段階的な導入
- 試験導入: 小さなプロジェクトから開始
- 設定の標準化: チーム共通の設定テンプレートを作成
- ドキュメント整備: セットアップ手順と 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は単なる技術ツールではなく、チーム開発の生産性を根本的に向上させる投資です。ぜひプロジェクトに導入して、その効果を実感してください。