【Dockerfile】Node.js / TypeScript のコンテナを作成する

# はじめに
この記事では、Dockerfile からビルドしたイメージを元に Node.js / TypeScript のコンテナを作成する手順について記載します。

以下の記事の Node.js / TypeScript バージョンです。

https://qiita.com/Yasushi-Mo/items/0c6ed9be966f764ff7ca

# 開発環境
開発環境は以下の通りです。

- Windows11
- Docker Engine 26.1.1
- Node.js 20.15.1
- npm 10.8.2
- @types/node 20.14.10
- ts-node 10.9.2
- TypeScript 5.5.3

# Node.js / TypeScript アプリケーションの作成
まずは、Node.js / TypeScript アプリケーションを作成します。

## `package.json` の作成
`package.json` を作成します。

https://docs.npmjs.com/cli/v10/commands/npm-init

コマンド実行中の質問をスキップするため、`-y` をつけて実行します。

```powershell
npm init -y
```

`package.json` が作成されます。

```package.json
{
  "name": "node",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}
```

## TypeScript のインストールと設定
TypeScript 及び以下のパッケージをインストールします。

- `ts-node` : TypeScript を Node.js で直接実行できるようにする
- `@types/node` : Node.js の型定義

https://typestrong.org/ts-node/

https://github.com/DefinitelyTyped/DefinitelyTyped

```powershell
npm install typescript ts-node @types/node --save-dev
```

`package.json` にインストールしたパッケージが表示されます。

```package.json
{
  ...
  "devDependencies": {
    "@types/node": "^20.14.10",
    "ts-node": "^10.9.2",
    "typescript": "^5.5.3"
  }
}
```

また、`node_modules` が作成されるので `.gitignore` を作成し、`node_modules` を追加して、git 管理対象外とします。

```.gitignore
node_modules
```

次に TypeScirpt 設定ファイルを作成します。

https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options

```powershell
npx tsc --init
```

`tsconfig.json` が作成されます。

デフォルトでは、`CommonJS` を利用する設定になっていますが、`ES Modules` (`ECMA Script Modules`) を利用したいので、設定を変更します。今回は Node.js 20 系を利用するので、20 系に対応した `lib`、`module`、`target` を指定します。

https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping

```tsconfig.json
{
  "compilerOptions": {
    /* Language and Environment */
    "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
    "lib": [
      "ES2023"
    ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,

    /* Modules */
    "module": "node16" /* Specify what module code is generated. */,

    /* Interop Constraints */
    "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
    "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,

    /* Type Checking */
    "strict": true /* Enable all strict type-checking options. */,

    /* Completeness */
    "skipLibCheck": true /* Skip type checking all .d.ts files. */
  }
}

```

## アプリケーションコードの実装
`src/index.ts` ファイルを作成し、ログを出力するコードを実装します。

```src/index.ts
import http from "http";

const HOSTNAME = "0.0.0.0";
const PORT = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader("Content-Type", "text/plain");
  res.end("Hello World");
});

server.listen(PORT, HOSTNAME, () => {
  console.log(`Server running at http://${HOSTNAME}:${PORT}`);
});

```

## エントリーポイントの変更と実行スクリプトの追加
`package.json` でエントリーポイントの変更とアプリケーションを実行するスクリプトの追加を行います。

```package.json
{
  ...
  "main": "src/index.ts",
  "scripts": {
    "start": "ts-node src/index.ts",
    ...
  ...
}
```

スクリプトを実行して、一度動作確認します。

```powershell
npm start
```

実装通りログが表示されることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/a197a9d0-3f15-73a1-2c9f-8abc3deb194f.png)


# Dockerfile の作成
Dockerfile を作成し、イメージをビルドします。

## ベースイメージ `FROM`
ベースイメージはタグが [20-alpine3.19](https://github.com/nodejs/docker-node/blob/73d1fd5cb366c132572dea5cb5a1ad39e1b00d2b/20/alpine3.19/Dockerfile) の Node.js イメージを利用します。

https://hub.docker.com/_/node

```Dockerfile
FROM node:20-alpine3.19
```

## 作業ディレクトリ `WORKDIR`
作業ディレクトリを `/app` にします。

```Dockerfile
WORKDIR /app
```

:::note
デフォルトでもルートディレクトリ（`/`）が作業ディレクトリとなるので、このインストラクションは任意です。ただ、作業ディレクトリを指定することで、Dockerfile のこれ以降の `COPY` や `RUN` などのインストラクションによって生成されるディレクトリやファイルが `app` 内に配置されます。そのため、コンテナ内の他のファイルと区別しやすくなります。
:::

## ホストマシンのファイルをコピー `COPY`
ホストマシンの `package.json` 及び `package-lock.json` をコンテナの作業ディレクトリにコピーします。

```Dockerfile
COPY package*.json ./
```

:::note
現時点で本インストラクションを記載しておくことで、次に実施する依存関係のインストールでキャッシュを利用でき、ビルドスピードが向上します。
※`package.json` や `package-lock.json` に変更があった時だけイメージビルド時、依存関係がインストールされます。
:::

## 依存関係のインストール `RUN`
先ほどコピーした `package.json` に記載されたパッケージ及びその依存関係をコンテナにインストールします。

```Dockerfile
RUN npm install
```

## ホストマシンの全ファイルをコピー `COPY`
ホストマシンの全ファイルをコンテナの作業ディレクトリにコピーします。

```Dockerfile
COPY . .
```

## コンテナのポートを指定 `EXPOSE`
3000 ポートを開けます。

```Dockerfile
EXPOSE 3000
```

## コンテナ起動時のコマンドを指定 `CMD`
コンテナ起動時に `package.json` の `scripts` の `start` を実行します。

```Dockerfile
CMD ["npm", "start"]
```

## イメージのビルド
完成した Dockerfile は以下の通りです。

```Dockerfile
FROM node:20-alpine3.19
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
```

こちらの Dockerfile からイメージをビルドします。

```powershell
docker image build --tag node-app:0.0.1 .
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/cfed30fb-393c-b842-e09b-220fb53a478b.png)


イメージがビルドできたか確認します。

```powershell
docker image ls node-app
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/89fe1a43-5445-1421-deac-caddbc4a2578.png)

# コンテナの起動
ビルドしたイメージからコンテナを起動します。

```powershell
docker container run `
--name app           `
--rm                 `
--publish 3000:3000  `
node-app:0.0.1
```

Dockerfile 作成前にホストマシンで動作確認したときと同じログが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/780dc9be-c245-3966-7f2a-562678e4be95.png)


# 参考
- [Definitely Typed](https://github.com/DefinitelyTyped/DefinitelyTyped)
- [node - Official Image](https://hub.docker.com/_/node)
- [Node Target Mapping](https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping)
- [npm-init](https://docs.npmjs.com/cli/v10/commands/npm-init)
- [ts-node](https://typestrong.org/ts-node/)
- [tsc CLI Options](https://www.typescriptlang.org/docs/handbook/compiler-options.html)

# 関連リンク

https://github.com/yasushi-mo/docker-samples/tree/main/node