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

Docker フルスタック開発環境構築: React + Express + PostgreSQL モノレポ入門

Last updated at Posted at 2025-09-24

はじめに

業務や本番運用を見据えた、モノレポ構成のフロントエンド + バックエンド + DB環境 を Docker で構築します。

  • フロントエンド: React + TypeScript + Vite

  • バックエンド: Express + TypeScript

  • データベース: PostgreSQL

  • 開発用はホットリロードあり

  • 本番用は軽量化

を実現します。

モノレポ構成とは何か?

モノレポ(Monorepo)とは、複数のプロジェクトやサービスを1つのリポジトリで管理する構成のことです。

特徴

  • 1つのリポジトリで複数サービスを管理
    例:フロントエンド、バックエンド、ライブラリ、DBスキーマなど
  • 依存関係を明示的に管理
    共通ライブラリや型定義を容易に共有可能
  • ビルドやデプロイの一元化が可能
    1つのCI/CDパイプラインで複数サービスを扱える

プロジェクトの準備

下記をpowerShellで実行する。

  • DockerFile, docker-compose.ymlなどの設定を追加する部分も含むため、結構長い。
# =====================================
# プロジェクトルート作成
# =====================================
mkdir my-app
cd my-app

# =====================================
# backend ディレクトリ作成・初期化
# =====================================
mkdir backend
cd backend

# npm 初期化
npm init -y                         # package.json 作成

# 依存パッケージインストール
npm install express                  # 本番依存
npm install -D typescript ts-node nodemon @types/node @types/express  # 開発用依存

# TypeScript 初期化
npx tsc --init                       # tsconfig.json 作成

# src ディレクトリ作成
mkdir src

# index.ts 作成(Express サーバーの最小構成)
Set-Content -Path src\index.ts -Value @'
import express from "express";

const app = express();
const port = 4000;

app.get("/", (_req, res) => res.send("Hello from Backend!"));

app.listen(port, () => console.log(`Backend running on port ${port}`));
'@

# Dockerfile.dev 作成(開発用)
Set-Content -Path Dockerfile.dev -Value @'
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
'@

# Dockerfile.prod 作成(本番用)
Set-Content -Path Dockerfile.prod -Value @'
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm install --only=production
CMD ["npm", "run", "start"]
'@

cd ..

# =====================================
# frontend ディレクトリ作成・初期化(完全非対話式)
# =====================================
# Vite + React + TypeScript を作成
npx create-vite@latest frontend -- --template react-ts
cd frontend
npm install

# Dockerfile.dev 作成(開発用)
Set-Content -Path Dockerfile.dev -Value @'
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
'@

# Dockerfile.prod 作成(本番用)
Set-Content -Path Dockerfile.prod -Value @'
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

COPY ./nginx.conf /etc/nginx/conf.d/default.conf
'@

cd ..

# =====================================
# db ディレクトリ作成・初期化SQL作成
# =====================================
mkdir db
Set-Content -Path db\init.sql -Value @'
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);
'@

# =====================================
# docker-compose 作成(開発用 / 本番用)
# =====================================

# 開発用 docker-compose
Set-Content -Path docker-compose.dev.yml -Value @'
version: "3.9"

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    volumes:
      - ./backend:/app
      - /app/node_modules
    ports:
      - "4000:4000"
    depends_on:
      - db

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "5173:5173"
    depends_on:
      - backend

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: appdb
    ports:
      - "5433:5432"
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  db_data:
'@

# 本番用 docker-compose
Set-Content -Path docker-compose.prod.yml -Value @'
version: "3.9"

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
    ports:
      - "4000:4000"
    depends_on:
      - db

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.prod
    ports:
      - "80:80"
    depends_on:
      - backend

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: appdb
    ports:
      - "5433:5432"
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:
'@

# =====================================
# git 初期化 & .gitignore 作成
# =====================================
git init
Set-Content -Path .gitignore -Value @'
# Node.js 標準
node_modules/
dist/
.env

# backend
/backend/node_modules
/backend/dist

# frontend
/frontend/node_modules
/frontend/dist
'@

補足

下記コマンドで対話形式の設定を要求されるので、添付画像のように選択してください。

npx create-vite@latest frontend -- --template react-ts

スクリーンショット 2025-09-23 214103.png

backendのtsconfig.jsonの内容を手動で設定必要

デフォルトだとコメントアウトされている "outDir": "./dist" のコメントアウトを外す。

backend/tsconfig.json
{
  "compilerOptions": {
    "outDir": "./dist",
    "module": "nodenext",
    "target": "esnext",
    "types": [],
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
  }
}

補足

"outDir": "./dist" は、TypeScript のコンパイル結果(.js ファイルや型定義ファイルなど)を出力するディレクトリを指定しています。

  • TypeScript では、tsconfig.json に書かれた設定に従って .ts ファイルが .js にコンパイルされます。
  • デフォルトではコンパイル結果はソースファイルと同じ階層に出力されるため、ソースとコンパイル済みファイルが混在してしまいます。
  • outDir を指定することで、コンパイル結果をまとめて別ディレクトリ(ここでは ./dist)に出力でき、管理がしやすくなります。
  • 例えば Docker イメージに組み込む際や、本番環境で Node.js から実行する際には、このディレクトリをそのまま参照できるため便利です。

本番用 nginx.conf の設定

下記ファイルをfrontend直下に作成

frontend/nginx.conf
server {
  listen 80;

  root /usr/share/nginx/html;
  index index.html;

  # React Router 対応
  location / {
    try_files $uri /index.html;
  }

  # API のプロキシ設定
  location /api/ {
    proxy_pass http://backend:4000/;
  }
}

補足

  • React SPA のルーティング対応

    • ブラウザで /users にアクセスしても、サーバー側にそのファイルは存在しないので index.html を返す
    • React Router が URL に応じて適切なコンポーネントをレンダリングする
  • API プロキシ

    • フロントとバックエンドが別コンテナでも、同じ nginx を経由して通信可能
    • CORS の問題も解消しやすい(フロントから見ると同一オリジンに見える)
  • Docker Compose と連携

    • proxy_pass http://backend:4000/; の backend は Compose のサービス名
    • ローカルの localhost:4000 ではなく、コンテナ間ネットワーク上の名前で通信

vite.config.tsの設定

frontend/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    // コンテナ内で開発用サーバーを立てるため、外部からアクセスできるように 0.0.0.0 を指定
    host: '0.0.0.0',

    watch: {
      // WSL2 や Docker 環境ではファイル変更イベント (inotify) がうまく伝わらないことがある
      // その場合、ポーリング方式に切り替えることでホットリロードを確実に動作させる
      usePolling: true,
    },
  },
});

package.jsonの内容を手動で設定必要

frontend, backend 共にscripts,typeを後述の内容に合わせてください。

プロジェクト構成

my-app/
├── .gitignore                   # Git で管理しないファイル/フォルダを指定
├── docker-compose.dev.yml       # 開発用 docker-compose 設定
├── docker-compose.prod.yml      # 本番用 docker-compose 設定
├── backend/                     # バックエンド(Express + TypeScript
   ├── Dockerfile.dev           # 開発用 Dockerfile
   ├── Dockerfile.prod          # 本番用 Dockerfile
   ├── package.json             # バックエンド依存関係
   ├── package-lock.json
   ├── tsconfig.json            # TypeScript 設定
   └── src/                     # バックエンドソスコ
       ├── index.ts             # エントリポイント
       └── routes/
           └── users.ts         #  API
├── frontend/                    # フロントエンド(React + Vite
   ├── Dockerfile.dev           # 開発用 Dockerfile
   ├── Dockerfile.prod          # 本番用 Dockerfile
   ├── nginx.conf               # Nginx 設定
   ├── package.json             # フロント依存関係
   ├── package-lock.json
   ├── tsconfig.json            # 共通 TS 設定
   ├── tsconfig.app.json        # アプリ用 TS 設定
   ├── tsconfig.node.json       # Vite  TS 設定
   ├── vite.config.ts           # Vite 設定
   ├── eslint.config.js         # ESLint 設定
   ├── index.html               # SPA エントリポイント
   ├── public/
      └── vite.svg             
   └── src/
       ├── main.tsx             # React エントリポイント
       ├── App.tsx              # アプリ全体のルティング
       ├── App.css              
       ├── index.css            # グロバルCSS
       ├── assets/              # アセット(画像など)
          └── react.svg
       └── pages/               # ジコンポネント
           ├── Home.tsx         
           ├── Home.css         
           └── Users.tsx        
├── db/                          # タベス関連
   └── init.sql                 # コンテナ初期化時に流すSQL(テブル定義, seedタなど)

.gitignore

.gitignore
# 共通
node_modules/
dist/
.env

# backend 固有
/backend/node_modules
/backend/dist

# frontend 固有
/frontend/node_modules
/frontend/dist

補足

node_modules を Git 管理すると、リポジトリが巨大になったり、OS や環境によって動作が変わったり、差分管理が困難になるため、基本的には Git に含めない ことが推奨されます。
必要な依存関係は package.json と package-lock.jsonで管理し、開発者は各自の環境で npm install で再現可能です。

Backend (Express + TypeScript)

package.json のスクリプト

backend/package.json
{
  "type": "module",
  "scripts": {
    "dev": "nodemon --watch src --ext ts --exec \"node --loader ts-node/esm src/index.ts\" --legacy-watch",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

dev は nodemon + ts-node を使用してホットリロード。

補足

  • ts-node
    TypeScript のファイルを コンパイルせずにそのまま実行できるツール。開発中の素早い確認やテストに便利。
  • nodemon
    コードの変更を監視して 自動で再実行してくれるツール。開発時の手動再起動を省ける。
  • --legacy-watch
    WSL2 上で Docker を動かしている場合、ファイルの変更検知に失敗することがあります。特に Windows 側のディレクトリをマウントしている場合 に発生しやすいです。
    これは、WSL2 のファイルシステムが inotify イベントを正しく伝えられない。
    →そのため nodemon / vite などが ファイル変更を検知できないといった理由です。
    このようなときに --legacy-watch を付けると、inotify ではなく ポーリング方式 で監視するため、ホットリロードが機能するようになります。

    純粋な Linux → 基本的に --legacy-watch は不要
    WSL2 / Docker Desktop (Mac/Win) → ファイル監視が弱いため、--legacy-watch が役立つ

Dockerfile.dev(開発用)

backend/Dockerfile.dev
# Node.js 20 をベースイメージとして使用
FROM node:20

# 作業ディレクトリを /app に設定
WORKDIR /app

# package.json と package-lock.json をコンテナにコピー
# 依存関係をインストールする前にコピーすることで
# Docker キャッシュを活用でき、再ビルドを高速化
COPY package*.json ./

# npm install を実行して依存パッケージをインストール
RUN npm install

# 残りのアプリケーションコードをコンテナにコピー
COPY . .

# コンテナ起動時に実行するコマンド
# 開発用なので nodemon や vite の dev サーバーを実行
CMD ["npm", "run", "dev"]

Dockerfile.prod(本番用)

backend/Dockerfile.prod
# ===========================
# ビルドステージ
# ===========================
# Node.js 20 をベースにしたビルド用イメージ
FROM node:20 AS builder

# 作業ディレクトリを /app に設定
WORKDIR /app

# 依存関係のインストールに必要な package.json と package-lock.json をコピー
COPY package*.json ./

# 依存パッケージをインストール(開発用も含む)
RUN npm install

# 残りのソースコードをコピー
COPY . .

# アプリケーションをビルド(例:TypeScript をコンパイル)
RUN npm run build

# ===========================
# 本番ステージ
# ===========================
# 軽量な Node.js イメージを使用
FROM node:20-slim

# 作業ディレクトリを /app に設定
WORKDIR /app

# ビルド済みの成果物を builder からコピー
COPY --from=builder /app/dist ./dist

# 依存関係の情報をコピー(本番用のみ)
COPY package*.json ./

# 本番用依存パッケージのみインストール
RUN npm install --only=production

# コンテナ起動時に実行するコマンド
CMD ["npm", "run", "start"]

補足

  • ビルドステージ (builder)
    TypeScript のコンパイルやフロントエンドのビルドなどを行う
    開発用依存もインストール
    ビルド成果物(dist)だけを本番ステージに渡す

  • 本番ステージ
    軽量イメージを使用して最小限のコンテナを作る
    本番依存だけをインストール
    ビルド済み成果物のみ配置してアプリを起動

Frontend (React + Vite)

package.json のスクリプト

frontend/package.json
{
  "scripts": {
    "dev": "vite --host 0.0.0.0",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  }
}

開発は vite でホットリロード。

補足

  • vite --host 0.0.0.0 の意味
    • デフォルトでは Vite は localhost だけで待ち受ける
      0.0.0.0 を指定すると 外部IPからもアクセス可能 になる
      例:Docker コンテナ内で起動してホストPCのブラウザからアクセス可能
    • 即時リロード
      Vite は ファイル変更を監視して、自動でブラウザを更新
      通常の HTML/JS の変更も即時反映
      React や Vue の場合は HMR によりコンポーネント単位で差分更新されるのでページ全体のリロードなしで反映される

  • "tsc -b && vite build"の意味
    • tsc -b
      TypeScript プロジェクトを ビルドモードでコンパイル
      tsconfig.json の設定に従い、型チェックと JS 出力を行う
    • vite build
      Vite が 最適化済みのフロントエンド静的ファイル( .js )を生成
      出力先は通常 dist/ フォルダ

Dockerfile.dev

frontend/Dockerfile.dev
# Node.js 20 をベースにしたイメージを使用
FROM node:20

# 作業ディレクトリを /app に設定
WORKDIR /app

# 依存関係の定義ファイルをコピー
COPY package*.json ./

# npm install で依存パッケージをインストール(開発用も含む)
RUN npm install

# ソースコード全体をコンテナ内にコピー
COPY . .

# コンテナ起動時に開発用サーバーを起動(即時リロード付き)
CMD ["npm", "run", "dev"]

Dockerfile.prod

backend/Dockerfile.prod
# ============================
# 1. ビルド用の Node 環境を準備
# ============================
# Node.js 20 の公式イメージを使用
FROM node:20 AS builder

# 作業ディレクトリを /app に設定
WORKDIR /app

# 依存関係の定義ファイルをコピー
COPY package*.json ./

# 依存関係をインストール
RUN npm install

# プロジェクト全体をコピー
COPY . .

# React/Vite プロジェクトをビルドして dist フォルダを生成
RUN npm run build


# ============================
# 2. ビルド結果を nginx に配置
# ============================
# 軽量な nginx アルパインイメージを使用
FROM nginx:alpine

# 先ほどビルドした dist フォルダを nginx の公開ディレクトリにコピー
COPY --from=builder /app/dist /usr/share/nginx/html


# ============================
# 3. カスタム nginx 設定を反映
# ============================
# 自分で用意した nginx.conf を nginx のデフォルト設定に上書き
COPY ./nginx.conf /etc/nginx/conf.d/default.conf


# Nginx が自動的にポート 80 で公開するので CMD は不要

DB (PostgreSQL)

db/init.sql

db/init.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

ここにCREATE文などDB初期化のSQLを追記していく。

Docker Compose 設定ファイル

docker-compose.dev.yml (開発用)

docker-compose.dev.yml
version: "3.9"

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    volumes:
      - ./backend:/app  # ホスト側 ./backend をコンテナ /app にマウント
      - /app/node_modules # マウントしないとホスト側とコンテナ側の依存が衝突する
    ports:
      - "4000:4000"  # ホスト:コンテナ (左側を変えるとホストからアクセスする際のポートを変更可能)
    depends_on:
      - db

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "5173:5173"  # ホスト:コンテナ (左側を変えるとホストからアクセスする際のポートを変更可能)
    depends_on:
      - backend

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: appdb
    ports:
      - "5433:5432"  # ホスト:コンテナ (左側を変えるとホストからアクセスする際のポートを変更可能)
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  db_data: # 永続化ボリューム

docker-compose.prod.yml (本番用)

docker-compose.prod.yml
version: "3.9"

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
    ports:
      - "4000:4000"
    depends_on:
      - db

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.prod
    ports:
      - "80:80"
    depends_on:
      - backend

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: appdb
    ports:
      - "5433:5432"
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

開発フロー例

実行手順

開発用ビルド & 起動

docker compose -f docker-compose.dev.yml build
docker compose -f docker-compose.dev.yml up

開発用 Compose ファイルで、コンテナ・イメージ・ボリュームを全て削除

docker compose -f docker-compose.dev.yml down --rmi all -v

本番用ビルド & 起動

docker compose -f docker-compose.prod.yml up --build -d

本番用 Compose ファイルで、コンテナ・イメージ・ボリュームを全て削除

docker compose -f docker-compose.prod.yml down --rmi all -v

API の追加方法 (Backend)

1. CORSの設定

backend/src/index.ts
import cors from "cors";
// // 開発用:全オリジンを許可
// app.use(cors());
app.use(cors({ origin: "http://localhost:5173" }));
補足

corsモジュールは下記でインストール

npm install cors
npm install -D @types/cors

2. ルートファイル作成

backend/src/routes/user.ts
import { Router } from "express";
import { Pool } from "pg";

const router = Router();

// PostgreSQLの接続設定
const pool = new Pool({
    host: "db",
    port: 5432,
    user: "user",
    password: "pass",
    database: "appdb",
});

// GET /users
router.get("/", async (_req, res) => {
    try {
        const result = await pool.query("SELECT id, name FROM users");
        res.json(result.rows);
    } catch (err) {
        console.error(err);
        res.status(500).json({ error: "Internal server error" });
    }
});

export default router;
補足
  • コンテナ間通信のため、ホスト名はサービス名の'db'を使用
  • ポートもコンテナ内の5432を使用

3. エントリポイントに組み込み

backend/src/index.ts
import userRouter from "./routes/user";
app.use("/api/users", userRouter);

上記の追記を行ったindex.ts

backend/src/index.ts
import express from "express";
import cors from "cors";
import userRouter from "./routes/users.js";
const app = express();
const PORT = 4000;

// 全てのオリジンを許可
app.use(cors());

// 特定オリジンのみ許可
// 開発用
// app.use(cors({ origin: "http://localhost:5173" }));

// 本番用
// app.use(cors({ origin: "http://localhost" }));

app.get("/", (req, res) => {
    res.send("Hello World");
});
app.use("/api/users", userRouter);
app.listen(PORT, () => {
    console.log(`Backend running on port ${PORT}`);
});
補足

4. 動作確認

curl http://localhost:4000/api/users

画面の追加方法 (Frontend)

1. ページコンポーネント作成

Home.tsx(デフォルトで作成されるApp.tsx ほぼそのままです)

frontend/src/pages/Home.tsx
import { useState } from 'react'
import reactLogo from '../assets/react.svg'
import viteLogo from '/vite.svg'
import './Home.css'

function Home() {
    const [count, setCount] = useState(0)

    return (
        <>
            <div>
                <a href="https://vite.dev" target="_blank">
                    <img src={viteLogo} className="logo" alt="Vite logo" />
                </a>
                <a href="https://react.dev" target="_blank">
                    <img src={reactLogo} className="logo react" alt="React logo" />
                </a>
            </div>
            <h1>Vite + React</h1>
            <div className="card">
                <button onClick={() => setCount((count) => count + 1)}>
                    count is {count}
                </button>
                <p>
                    Edit <code>src/App.tsx</code> and save to test HMR
                </p>
            </div>
            <p className="read-the-docs">
                Click on the Vite and React logos to learn more
            </p>
        </>
    )
}

export default Home

Users.tsx(API呼び出しを行うページコンポーネント)

frontend/src/pages/Users.tsx
import { useEffect, useState } from "react";

export default function Users() {
    const [users, setUsers] = useState<{ id: number; name: string }[]>([]);

    useEffect(() => {
        // backendサーバー (http://localhost:4000/api/users) に GET リクエストを送る
        fetch("http://localhost:4000/api/users")
            // サーバーからのレスポンス (Response オブジェクト) を JSON に変換する
            .then((res) => res.json())
            // JSON に変換したデータを setUsers に渡して state を更新する
            .then(setUsers);
    }, []);

    return (
        <div>
            <h1>ユーザー一覧</h1>
            <ul>
                {users.map((u) => (
                    <li key={u.id}>{u.name}</li>
                ))}
            </ul>
        </div>
    );
}

2. ルーティングに追加

frontend/src/App.tsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Users from "./pages/Users";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* http://localhost:5173/ でアクセス可能 */}
        <Route path="/" element={<Home />} />
        {/* http://localhost:5173/users でアクセス可能 */}
        <Route path="/users" element={<Users />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

補足:react-router-domモジュールは下記でインストール

npm install react-router-dom@6
npm install -D @types/react-router-dom

ブラウザで確認

http://localhost:5173/users

本番用なら

http://localhost:80/users

予め、データを格納してからテスト
image.png

image.png

トラブルシューティング:Dockerでモジュールが認識されない場合

開発中に pg をインストールしても Node.js 側で認識されず、Docker コンテナがクラッシュすることがありました。原因と解決手順を整理します。

  • 現象

Docker Compose で backend コンテナを起動するとアプリがクラッシュ

pg モジュールを import すると以下のようなエラーが出る

node:internal/modules/run_main:123
triggerUncaughtException(...)
[Object: null prototype] { ... }

pg を import しない場合は正常に動作する

  • 原因
    原因は Docker の キャッシュや古いボリューム にありました。

    • 古い Node モジュール
      Docker コンテナ内で /app/node_modules をボリュームマウントしている場合、新しくインストールした pg が反映されないことがあります。

    • 古い DB ボリューム
      PostgreSQL コンテナが以前のボリューム(古いデータ)を使用していると、接続設定が正しくても動作しない場合があります。

    • Docker イメージキャッシュ
      古いビルドキャッシュが残っていると、新しい依存関係が正しく反映されません。

  • 解決方法

開発環境を完全リセットして再構築することで解決しました。

# 1. 現在起動中の Docker Compose サービスを停止して削除
sudo docker compose down
# → コンテナ、ネットワーク、デフォルトで作成されたボリュームは削除される(ただし named volumes は残る)

# 2. 不要な Docker リソースを一括削除
sudo docker system prune -a --volumes
# -a: 使用されていないすべてのイメージ、コンテナ、ネットワークを削除
# --volumes: 使用されていないボリュームも削除
# → 完全にクリーンな状態にすることで、古いイメージやキャッシュで起きる問題を防ぐ

# 3. 開発用 Docker Compose をビルドして起動
sudo docker compose -f docker-compose.dev.yml up --build

system prune -a --volumes により、未使用のコンテナ、イメージ、ネットワーク、ボリュームが全て削除されます

これでクリーンな状態で pg をインストールしたコンテナが起動でき、正常に動作するようになります

本番環境ではボリューム削除に注意してください。DB データが消える可能性があります。

  • まとめ

    • Docker で Node.js + PostgreSQL を組み合わせる場合、ボリュームやキャッシュの状態が依存関係の反映に大きく影響する

    • 開発環境でのトラブルはまず「キャッシュや古いボリュームのクリア」を検討する

    • 本番ではデータ消失に注意しつつ、必要に応じてイメージをビルドし直す

最後に

今回紹介した構成はあくまで 最小構成の例 です。
実際の業務環境では、以下の点に注意して設計をする必要があります。

  • localhost ではなくサービス名やドメインでの接続設定
  • バックエンド・フロントエンドの階層構造の整理
  • DB 接続やマイグレーションの自動化
  • CORS 設定やセキュリティ対策
  • 開発・本番環境での差分管理

これらについても後々深掘っていきたいと思います。

学習のために構築してみたものなので、不完全な部分もあるかと思います。
アドバイスを頂けたら幸いです。

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