はじめに
モダンなWeb開発において、Dockerを使ったコンテナ化は欠かせない技術となっています。本記事では、Node.js APIを題材に、DockerとDocker Composeを使った最小構成の開発環境構築を通して、コンテナ化の基礎を学びます。
ゴール
今回の記事で達成することは以下の通りです:
- Node.jsで簡単なAPIを作成する
- Dockerfileでビルド・起動できるようにする
- docker-compose.ymlでAPI + PostgreSQL + Redisを一括起動する
プロジェクト構成
まず、以下のようなディレクトリ構成でプロジェクトを作成します:
myapp/
├── src/
│ └── index.js
├── package.json
├── Dockerfile
├── docker-compose.yml
1. 最小のNode.js APIを作成する
src/index.js
シンプルなExpressアプリケーションを作成します:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (_req, res) => {
res.send('Hello, world!');
});
app.listen(PORT, () => {
console.log(`API listening on port ${PORT}`);
});
package.json
プロジェクトの設計図となるpackage.jsonを作成します。これには名前、バージョン、依存関係、スクリプトなどの重要な情報が含まれます:
{
"name": "myapp",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
2. Dockerfileを作成する
Dockerfileは、コンテナイメージを構築するための設計図です:
FROM node:22.1.0-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Dockerfileの仕組み解説
このDockerfileには重要な最適化技術が含まれています:
レイヤーキャッシュの活用
-
COPY package*.json ./
を先に実行する理由は、ビルド高速化にあります - package.jsonが変更されていない場合、
npm ci
の実行をスキップできます - これにより、ソースコードのみ変更した場合の再ビルドが大幅に高速化されます
レイヤーキャッシュとは
Dockerイメージは複数の「レイヤー」で構成されており、Dockerfileの各命令(FROM、COPY、RUNなど)が1つのレイヤーを作成します。Dockerは一度作成されたレイヤーをキャッシュとして保存し、同じ命令が再度実行される際に、変更がなければキャッシュされたレイヤーを再利用します。
例えば、以下の順序でDockerfileが実行される場合:
-
FROM node:22.1.0-slim
→ ベースイメージレイヤー -
COPY package*.json ./
→ パッケージファイルレイヤー -
RUN npm ci
→ 依存関係インストールレイヤー -
COPY . .
→ ソースコードレイヤー
ソースコードのみを変更した場合、手順1〜3のレイヤーはキャッシュから再利用され、手順4のみが再実行されます。もしpackage.jsonの後にソースコードをコピーしていた場合、ソースコード変更のたびにnpm ci
も再実行されてしまい、ビルド時間が大幅に増加してしまいます。
npm ciの使用
-
npm install
ではなくnpm ci
を使用することで、package-lock.jsonを確実に再現 - Clean Installを意味し、本番環境での再現性を保証します
EXPOSEの役割
-
EXPOSE 3000
はポート使用の宣言であり、実際のポート開放は別途設定が必要です
3. docker-compose.ymlを作成する
Docker Composeは複数のコンテナを一括管理するためのツールです:
version: "3.9"
services:
api:
build: .
ports:
- 3000:3000
environment:
- PORT=3000
depends_on:
- db
- redis
db:
image: postgres:16.2-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
ports:
- 5432:5432
redis:
image: redis:7.0.15-alpine
ports:
- 6379:6379
docker-compose.ymlの役割
複数コンテナの管理
- API、PostgreSQL、Redisの3つのコンテナを一括起動
- 各サービス間の依存関係も管理できます
depends_on
- 起動順序を制御し、データベースが起動してからAPIが起動するよう設定
ポートマッピング
-
ports: "ホスト側:コンテナ側"
の形式でポートを設定 - PostgreSQLのデフォルトポート5432番を使用
5432:5432とは、「ホスト側の5432番ポートへのリクエストを、コンテナ側の5432番ポートに転送する」という意味です。
例えば、curl localhost:5432というリクエストは、PostgreSQLコンテナの5432番ポートに転送されて処理されます。
4. アプリケーションの起動
以下のコマンドでアプリケーションを起動します:
docker-compose up --build
起動時の動作
- 3つのコンテナが起動:api、db、redisコンテナが順次起動
- イメージの処理:apiのみビルド、db/redisは既存イメージをダウンロード
- アプリケーション実行:apiコンテナ内でnpm start → node src/index.js実行
- アクセス可能:localhost:3000でExpressサーバーにアクセス可能
ブラウザで http://localhost:3000
を開き、"Hello, world!" が表示されれば成功です。
よくある問題と解決方法
npm ciエラー
package-lock.jsonが存在しない場合に発生します。ローカル環境で一度npm install
を実行してpackage-lock.jsonを生成してください。
ポート競合エラー
PostgreSQLの5432番ポートが既に使用中の場合、docker-compose.ymlで以下のように変更できます:
db:
image: postgres:16.2-alpine
ports:
- 5433:5432 # ホスト側のポートを5433に変更
Dockerの基本概念
コンテナとは
コンテナは軽量な仮想サーバーのようなもので、アプリケーション専用の箱と考えることができます。仮想マシンとの違いは、OS全体を仮想化するのではなく、アプリケーションレベルでの仮想化を行う点です。
Docker Engineの役割
Docker Engineは、ローカルマシンとコンテナを繋ぐ中核的な仕組みです。ホストOSのカーネル・ファイルシステムを共有しつつ、標準化された環境を提供します。
次のステップ
基本的な構成ができたら、以下のような改善を検討してみてください:
- Dockerfileをマルチステージにする:ビルド用と実行用の環境を分離
- 開発/本番切り替えを可能にする:環境ごとの設定ファイルを作成
- devcontainer化する:VS Codeでの開発体験を向上
まとめ
本記事では、Docker ComposeとNode.jsを使った最小構成のAPI開発環境を構築しました。コンテナ化により、環境の標準化、ポータビリティの向上、チーム開発での環境統一など、多くのメリットを享受できます。
まずは今回の最小構成から始めて、徐々に機能を拡張していくことで、実際のプロジェクトで活用できるスキルを身につけることができるでしょう。
最後に
株式会社シンシアでは、実務未経験のエンジニアの方や学生エンジニアインターンを採用し一緒に働いています。
※ シンシアにおける働き方の様子はこちら
シンシアでは、年間100人程度の実務未経験の方が応募し技術面接を受けます。その経験を通し、実務未経験者の方にぜひ身につけて欲しい技術力(文法)をここでは紹介していきます。