0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

マルチリポジトリ構成で Docker Compose + Dev Container を使った開発環境の構築

0
Posted at

はじめに

ポートフォリオサイトの開発環境を構築する際、複数のリポジトリを跨ぐマイクロサービス構成にしたいという要件がありました。具体的には、開発用の共通設定を管理する infrastructure リポジトリと、実際のアプリケーションコードを格納する portal リポジトリに分けて管理する形です。

この記事では、Docker Compose と VS Code の Dev Container 機能を組み合わせて、以下を実現する開発環境の構築方法を紹介します。

  • Spring Boot バックエンド
  • Vite + React フロントエンド
  • PostgreSQL データベース
  • Nginx リバースプロキシ
  • VS Code での統合開発環境

全体構成

まず、全体のディレクトリ構成と Docker 環境の関係を図示します。

ディレクトリ構造は以下のようになっています:

portfolio/
├── infrastructure/        # インフラ設定リポジトリ
│   └── docker/
│       ├── docker-compose.all.yml
│       ├── rebuild-all.sh
│       └── nginx/
│           └── nginx.conf
│
└── portal/               # アプリケーションリポジトリ
    ├── backend/
    │   ├── Dockerfile.dev
    │   ├── .devcontainer/
    │   │   └── devcontainer.json
    │   └── src/...
    │
    └── frontend/
        ├── Dockerfile.dev
        ├── .devcontainer/
        │   └── devcontainer.json
        └── src/...

この構成にした理由は、Docker Compose ファイルや Nginx の設定など、複数のサービスで共通して使うインフラ設定を infrastructure リポジトリに集約し、各アプリケーションのコードは portal リポジトリで管理することで、責務を明確に分離するためです。

Docker Compose の構成

サービス構成図

Docker Compose で管理する 4 つのサービスの関係性は以下の通りです。

docker-compose.all.yml の設計

infrastructure/docker/docker-compose.all.yml は全サービスを統合管理します。

version: "3.8"

services:
  # PostgreSQL
  db:
    image: postgres:16-alpine
    container_name: portfolio-db
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: portfolio
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: devpass
    volumes:
      - pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - portfolio-net

  # Portal Backend
  portal-backend:
    build:
      context: ../../portal/backend
      dockerfile: Dockerfile.dev
    container_name: portal-backend
    ports:
      - "8080:8080"
    volumes:
      - ../../portal/backend:/app
      - maven-cache:/root/.m2
    environment:
      - SPRING_PROFILES_ACTIVE=dev
      - DB_HOST=db
      - DB_PORT=5432
      - DB_NAME=portfolio
      - DB_USER=dev
      - DB_PASSWORD=devpass
    depends_on:
      db:
        condition: service_healthy
    networks:
      - portfolio-net

  # Portal Frontend
  portal-frontend:
    build:
      context: ../../portal/frontend
      dockerfile: Dockerfile.dev
    container_name: portal-frontend
    ports:
      - "5173:5173"
    volumes:
      - ../../portal/frontend:/app
      - node-modules:/app/node_modules
    environment:
      - VITE_API_URL=http://localhost:8080/api
    networks:
      - portfolio-net

  # Nginx
  nginx:
    image: nginx:alpine
    container_name: portfolio-nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - portal-backend
      - portal-frontend
    networks:
      - portfolio-net

volumes:
  pg-data:
  maven-cache:
  node-modules:

networks:
  portfolio-net:
    driver: bridge

ポイントは以下の通りです:

1. 相対パスでの参照

build:
  context: ../../portal/backend
  dockerfile: Dockerfile.dev

infrastructure リポジトリから portal リポジトリのディレクトリを相対パスで参照しています。これにより、リポジトリが分かれていても問題なくビルドできます。

2. ボリュームマウントでホットリロード

volumes:
  - ../../portal/backend:/app
  - maven-cache:/root/.m2

ホスト側のソースコードをコンテナにマウントすることで、ファイルを変更すると即座にコンテナ内に反映されます。また、Maven や npm のキャッシュは名前付きボリュームにすることで、ビルド時間を短縮しています。

3. ヘルスチェックと依存関係

depends_on:
  db:
    condition: service_healthy

データベースが完全に起動してから Backend を起動するように設定しています。これにより、接続エラーを防げます。

4. 開発用ポート公開

開発中は各サービスに直接アクセスできるよう、すべてのポートをホストに公開しています:

  • Nginx: 80
  • Backend: 8080
  • Frontend: 5173
  • Database: 5432

本番環境では Nginx の 80 ポートのみ公開すれば十分ですが、開発時はデバッグやテストのために各サービスへ直接アクセスできると便利です。

Dockerfile の設計

Backend (Spring Boot)

portal/backend/Dockerfile.dev は開発用に最適化しています。

FROM eclipse-temurin:21-jdk-alpine

# Dev Container用にbashとgitをインストール
RUN apk add --no-cache bash git

WORKDIR /app

# Mavenラッパーをコピー
COPY mvnw .
COPY .mvn .mvn

# 依存関係を先にダウンロード(キャッシュ効率化)
COPY pom.xml .
RUN ./mvnw dependency:go-offline

# ソースコードはボリュームマウントするのでCOPYしない

# Spring Boot DevToolsによる自動再起動を有効化
ENV SPRING_DEVTOOLS_RESTART_ENABLED=true

EXPOSE 8080

CMD ["./mvnw", "spring-boot:run"]

レイヤーキャッシュの活用

Dockerfile のレイヤーキャッシュを効率的に使うため、変更頻度が低い依存関係のダウンロードを先に実行しています。pom.xml が変わらない限り、この層はキャッシュされるため、再ビルドが高速になります。

Spring Boot DevTools

SPRING_DEVTOOLS_RESTART_ENABLED=true を設定することで、ファイル変更時の自動再起動が有効になります。実際に使ってみると、Java ファイルを保存してから数秒でアプリケーションが再起動するため、非常にスムーズです。

Frontend (Vite + React)

portal/frontend/Dockerfile.dev も同様に開発用に最適化しています。

FROM node:20-alpine

# Dev Container用にbashとgitをインストール
RUN apk add --no-cache bash git

WORKDIR /app

# package.jsonを先にコピー(キャッシュ効率化)
COPY package*.json ./
RUN npm install

# ソースコードはボリュームマウントするのでCOPYしない

EXPOSE 5173

CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

Vite の HMR (Hot Module Replacement)

--host 0.0.0.0 を指定することで、Docker コンテナ内からでも HMR が正常に動作します。これがないと、ファイルを変更してもブラウザに反映されないという問題が発生します。

node_modules の扱い

volumes:
  - ../../portal/frontend:/app
  - node-modules:/app/node_modules

ホスト側のディレクトリをマウントしつつ、node_modules は名前付きボリュームで上書きしています。これにより、Windows/Mac とコンテナ (Linux) 間での node_modules の互換性問題を回避できます。

Nginx のリバースプロキシ設定

infrastructure/docker/nginx/nginx.conf で、フロントエンドとバックエンドを統合しています。

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    upstream frontend {
        server portal-frontend:5173;
    }

    upstream backend {
        server portal-backend:8080;
    }

    server {
        listen 80;
        server_name localhost;

        # Frontend
        location / {
            proxy_pass http://frontend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }

        # Backend API
        location /api/ {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

WebSocket サポート

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;

Vite の HMR は WebSocket を使うため、これらのヘッダー設定が必要です。最初これを設定せずに動かしたところ、HMR が動作せず、かなり悩みました。

パスベースのルーティング

  • / → フロントエンド
  • /api/ → バックエンド

このシンプルなルーティングにより、CORS の問題も発生しません。すべて同一オリジンからのリクエストとして扱われるためです。

Dev Container の統合

VS Code の Dev Container 機能を使うと、コンテナ内で直接コーディングできます。

Backend の devcontainer.json

portal/backend/.devcontainer/devcontainer.json

{
  "name": "Portal Backend",
  "dockerComposeFile": "../../../infrastructure/docker/docker-compose.all.yml",
  "service": "portal-backend",
  "workspaceFolder": "/app",

  "customizations": {
    "vscode": {
      "extensions": [
        "vscjava.vscode-java-pack",
        "vmware.vscode-spring-boot",
        "redhat.java",
        "vscjava.vscode-maven"
      ],
      "settings": {
        "java.home": "/opt/java/openjdk",
        "editor.formatOnSave": true,
        "java.configuration.updateBuildConfiguration": "automatic"
      }
    }
  },

  "postCreateCommand": "./mvnw dependency:go-offline",
  "forwardPorts": [8080],
  "runServices": ["db", "portal-backend", "portal-frontend", "nginx"]
}

Frontend の devcontainer.json

portal/frontend/.devcontainer/devcontainer.json

{
  "name": "Portal Frontend",
  "dockerComposeFile": "../../../infrastructure/docker/docker-compose.all.yml",
  "service": "portal-frontend",
  "workspaceFolder": "/app",

  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "bradlc.vscode-tailwindcss",
        "dsznajder.es7-react-js-snippets"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.codeActionsOnSave": {
          "source.fixAll.eslint": true
        }
      }
    }
  },

  "postCreateCommand": "npm install",
  "forwardPorts": [5173],
  "runServices": ["db", "portal-backend", "portal-frontend", "nginx"]
}

重要なポイント

1. dockerComposeFile の相対パス

"dockerComposeFile": "../../../infrastructure/docker/docker-compose.all.yml"

.devcontainer ディレクトリからの相対パスで、別リポジトリの Docker Compose ファイルを参照しています。

2. runServices の指定

"runServices": ["db", "portal-backend", "portal-frontend", "nginx"]

Dev Container を開いたときに起動するサービスを明示的に指定しています。これにより、Backend の開発中でもフロントエンドや Nginx が同時に起動し、統合された環境で開発できます。

3. 開発用拡張機能の自動インストール

コンテナを開くと、指定した VS Code 拡張機能が自動的にコンテナ内にインストールされます。チーム開発で全員が同じツールセットを使えるため、非常に便利です。

開発フロー

起動方法

全サービスを一括で起動するスクリプトを用意しています。

infrastructure/docker/rebuild-all.sh

#!/bin/bash

echo "=========================================="
echo "Docker イメージ完全再ビルド"
echo "=========================================="
echo ""

cd "$(dirname "$0")"

echo "🛑 既存のコンテナとイメージを削除..."
docker-compose -f docker-compose.all.yml down -v

echo ""
echo "🗑️ 古いイメージを削除..."
docker rmi docker-portal-backend docker-portal-frontend 2>/dev/null || true

echo ""
echo "🔨 イメージを再ビルド(キャッシュなし)..."
docker-compose -f docker-compose.all.yml build --no-cache

echo ""
echo "🚀 サービスを起動..."
docker-compose -f docker-compose.all.yml up -d

echo ""
echo "⏳ コンテナ起動を待機中..."
sleep 5

echo ""
echo "✅ 起動状態確認:"
docker-compose -f docker-compose.all.yml ps

echo ""
echo "=========================================="
echo "完了!以下のURLにアクセスできます:"
echo "  - Frontend:  http://localhost/"
echo "  - Backend:   http://localhost/api/actuator/health"
echo "  - Database:  localhost:5432"
echo ""
echo "📊 ログ確認: docker-compose -f docker-compose.all.yml logs -f"
echo "🛑 停止: docker-compose -f docker-compose.all.yml down"
echo "=========================================="

実行方法:

cd infrastructure/docker
./rebuild-all.sh

開発時の流れ

実際の使用感としては:

  1. VS Code で portal/backend または portal/frontend を Dev Container で開く
  2. すべてのサービスが自動的に起動
  3. Java や TypeScript のファイルを編集
  4. 保存すると数秒でアプリケーションに反映
  5. http://localhost/ でブラウザ確認

この一連の流れがとてもスムーズで、従来のローカル環境に直接インストールする方法と比べて遜色ない開発体験が得られます。

トラブルシューティング

ポートが既に使用されている

Error: bind: address already in use

既存のプロセスを停止してから再起動します。

cd development/docker
docker-compose -f docker-compose.all.yml down
./rebuild-all.sh

HMR が動作しない

Vite の HMR が動作しない場合、以下を確認します:

  1. CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] が設定されているか
  2. Nginx の WebSocket 設定が正しいか
  3. ファイアウォールがポート 5173 をブロックしていないか

Maven の依存関係エラー

docker-compose -f docker-compose.all.yml down -v
./rebuild-all.sh

-v オプションで名前付きボリュームも削除し、依存関係を完全にクリーンな状態から再取得します。

まとめ

この構成により、以下のメリットが得られました:

  1. 環境の一貫性: チーム全員が同じ環境で開発できる
  2. セットアップの簡単さ: Docker Desktop と VS Code さえあれば、数分で開発開始できる
  3. ホットリロード: ファイル変更が即座に反映される快適な開発体験
  4. 本番環境に近い: Nginx、データベース込みで動作確認できる
  5. リポジトリ分離: インフラ設定とアプリコードを明確に分離管理

当初はマルチリポジトリ構成で Docker Compose と Dev Container を連携させるのが難しそうに感じましたが、相対パスをうまく活用することで、意外とシンプルに実現できました。

これから同様の構成を考えている方の参考になれば幸いです。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?