TypeScriptで書いたアプリケーションをコンテナにしてデプロイしたくなったのでやり方を記録します。
今回作ったアプリケーションはTwitterのBotなので、HTTPリクエストを起点に起動するアプリケーションではなく、常時起動していてタイムラインをストリーミングするようなものです。
そのため、ポートフォワーディングはしていません。
HTTPリクエストを受け取るアプリケーションの場合は、ポートの割当が必要となります。

Dockerfile

FROM node:14 AS builder
WORKDIR /app
COPY package*.json ./
RUN yarn install
COPY . .
RUN yarn build

FROM node:14 AS runner
WORKDIR /app
COPY --from=builder app/dist ./dist
COPY package*.json ./
RUN yarn install --production
# COPY .env ./
CMD ["yarn", "start"]


ビルド

docker build . -t [IMAGE_NAME]


実行

docker run -it --rm --name [CONTAINER_NAME] [IMAGE_NAME]


ビルド結果のイメージを軽くするために、マルチステージビルドを行います。
builderでは、devDependenciesを含めたすべての依存をインストールし、TypeScript → Node.jsへのコンパイルを行います。
runnerでは、builderでコンパイルされた成果物のdistディレクトリをコピーし、dependenciesの依存だけをインストールします。
成果物として得られるイメージには、コンパイル後のJSファイル、package(-lock).json、node_modules(dependenciesのみ)が含まれます。
コンテナを実行すると、yarn startが実行されてアプリケーションが起動します。

環境変数については、デプロイする環境によって、アプリケーションに注入する方法が異なると思います。
開発時は、ローカル環境で作成した.envをコンテナ内にコピーすることで動かしています。

参考

Node.js Web アプリケーションを Docker 化する
成果物のデプロイ