👋

TiDBローカル環境構築⑥

に公開

TiDBのローカル環境の構築を6回目行なっていきます。
前回TiDB環境のDjango構築が出来ましたが、今回はDockerでフロント側の構築を行っていきます。

コンテナ環境のツリー構成

DB構築時のツリー構造は以下のようにしてます。
webフォルダ配下にNestJSの環境を構築していきます。
またルート配下のdocker-compose.ymlでwebコンテナを作成します。

.
├── .env.api
├── .env.web
├── docker-compose.yml
└── src
    ├── infra
    │   └── db
    │       ├── Dockerfile
    │       ├── data
    │       │   └── test_db.sql
    │       ├── docker-compose.yml
    │       └── entrypoint.sh
    └── www
        ├── api
        │   ├── Dockerfile
        │   ├── docker-compose.yml
        │   ├── entrypoint.sh
        │   ├── project
        │   │   ├── core
        │   │   │   ├── __init__.py
        │   │   │   ├── admin.py
        │   │   │   ├── apps.py
        │   │   │   ├── migrations
        │   │   │   │   ├── 0001_initial.py
        │   │   │   │   └── __init__.py
        │   │   │   ├── models.py
        │   │   │   ├── serializers.py
        │   │   │   ├── urls.py
        │   │   │   └── views.py
        │   │   ├── manage.py
        │   │   └── project
        │   │       ├── __init__.py
        │   │       ├── asgi.py
        │   │       ├── settings.py
        │   │       ├── urls.py
        │   │       └── wsgi.py
        │   └── requirements.txt
        └── web
            ├── Dockerfile
            ├── app
            │   ├── package-lock.json
            │   ├── package.json
            │   ├── src
            │   │   ├── app.controller.ts
            │   │   ├── app.module.ts
            │   │   └── main.ts
            │   ├── tsconfig.build.json
            │   └── tsconfig.json
            ├── docker-compose.yml
            └── docker-entrypoint.sh

ソースコードを作成

webコンテナの各ファイルの追加と編集を行います。
ルートのdocker-compose.ymlを編集します。

docker-compose.yml
services:
  tiup-playground:
    extends:
      file: ./src/infra/db/docker-compose.yml
      service: tiup-playground
  api:
    extends:
      file: ./src/www/api/docker-compose.yml
      service: api
    depends_on:
      - tiup-playground
 # 以下を追加
  web:
    extends:
      file: ./src/www/web/docker-compose.yml
      service: web
networks:
  tidb-net:
    name: tidb-net
    driver: bridge



環境ファイルを作成します。

.env.web
PORT=3000
API_BASE=http://api:8000/api
API_TOKEN=



この Dockerfile は、Node.js 20 の軽量 Alpine イメージをベースに、NestJS CLI をグローバルインストールし、docker-entrypoint.sh をエントリーポイントとして設定して、ポート 3000 でアプリを起動できる環境を構築します。

./src/www/web/Dockerfile
FROM node:20-alpine

WORKDIR /app

RUN apk add --no-cache libc6-compat

RUN npm install -g @nestjs/cli

COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

EXPOSE 3000
ENTRYPOINT ["sh", "/usr/local/bin/entrypoint.sh"]



このスクリプトは /app に移動後、package.json がなければ NestJS プロジェクトを新規作成し、package-lock.json の有無で npm ci または npm install を実行して依存関係を整え、最後に指定ポートで NestJS を開発モード起動します。

./src/www/web/docker-entrypoint.sh
#!/bin/sh
set -e

cd /app

if [ ! -f package.json ]; then
  echo "[web] No package.json found. Bootstrapping a new Nest project into /app ..."
  nest new app --directory . --skip-git --skip-install --strict --package-manager npm
fi

if [ -f package-lock.json ]; then
  npm ci
else
  npm install
fi

export PORT="${PORT:-3000}"

echo "[web] Starting Nest in dev mode on port ${PORT} ..."
exec npm run start:dev



この docker-compose サービス定義は、web コンテナを Dockerfile からビルドし、/app を作業ディレクトリに設定、ホストの 3000 番ポートをコンテナの 3000 番にマッピング、.env.web から環境変数を読み込み、api サービス起動後に依存関係を npm ci または npm install で整えて NestJS を開発モードで起動する構成です。

./src/www/web/docker-compose.yml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    working_dir: /app
    ports:
      - "3000:3000"
    env_file:
      - ../../../.env.web
    depends_on:
      - api
    volumes:
      - ./app:/app
    command: >
      sh -lc "
        if [ -f package-lock.json ]; then npm ci; else npm install; fi
        && npm run start:dev
      "
    networks:
      - tidb-net



これは TypeScript の tsconfig.json 設定で、CommonJS モジュール形式・ES2021 ターゲット・デコレータ有効化・型チェック厳格化などを指定し、ソースを ./dist に出力する構成です。

./src/www/web/app/tsconfig.json
{
  "compilerOptions": {
    "module": "CommonJS",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2021",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "strict": true,
    "skipLibCheck": true,
    "moduleResolution": "node"
  }
}



これは NestJS の最小構成プロジェクト用 package.json で、アプリ名や依存関係、ビルド・起動・フォーマット用のスクリプトを定義しています。

./src/www/web/app/package.json
{
  "name": "nest-hello-app",
  "version": "0.1.0",
  "private": true,
  "description": "Minimal NestJS Hello World (HTML) app for Docker bind mount",
  "license": "MIT",
  "main": "dist/main.js",
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "start": "node dist/main.js",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\""
  },
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0",
    "reflect-metadata": "^0.2.0",
    "rxjs": "^7.8.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.0.0",
    "typescript": "^5.4.0",
    "ts-node": "^10.9.2",
    "@types/node": "^20.11.0",
    "prettier": "^3.2.0",
    "rimraf": "^5.0.5"
  }
}



NestJS のコントローラで、/ への GET リクエストに対して HTML を返すシンプルなサンプル実装です。

./src/www/web/app/src/app.controller.ts
import { Controller, Get, Header } from '@nestjs/common';

@Controller()
export class AppController {
  @Get()
  @Header('Content-Type', 'text/html; charset=utf-8')
  getHello(): string {
    return `
      <!DOCTYPE html>
      <html>
        <head><meta charset="UTF-8"><title>Hello Nest</title></head>
        <body>
          <h1>Hello World from NestJS!</h1>
          <p>This is a sample page served by NestJS inside Docker.</p>
        </body>
      </html>
    `;
  }
}


NestJS アプリのルートモジュールで、AppController を登録してアプリ全体の構成を定義しています。

./src/www/web/app/src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [],
})
export class AppModule {}



これは NestJS アプリのエントリーポイントで、AppModule からアプリを生成し、環境変数 PORT またはデフォルトの 3000 番ポートで待ち受けを開始します。

./src/www/web/app/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT || 3000);
  console.log(`Nest app is listening on port ${process.env.PORT || 3000}`);
}
bootstrap();



以下のURLにアクセスするとNestのトップページが確認できました。
http://127.0.0.1:3000/

まとめ

  • TiDB ローカル環境構築シリーズの第6回として、Docker上にNestJSフロント環境を追加した
  • webフォルダ配下にNestJSの最小構成を作成し、docker-compose.ymlでapiサービスと連携できるようにした
  • DockerfileでNode.js 20 (Alpine) + NestJS CLI 環境を用意し、docker-entrypoint.shで新規プロジェクト生成と依存関係インストールを自動化
  • tsconfig.jsonとpackage.jsonを設定し、最小構成の NestJS Hello World HTML ページを作成
  • ブラウザで http://127.0.0.1:3000/にアクセスし、NestJSのレスポンスを確認できた
コラボスタイル Developers

Discussion