初めに
前回はDockerの全体的なイメージについて解説しましたが、今回は実際にDockerを使ってNext.jsやデータベース(PostgreSQLなど)の環境を構築する手順について解説します。
前回の記事になります。
前提条件
この手順では、Next.js、Prisma、PostgreSQLを使用しています。
目次
- Dockerfileの作成
- docker-compose.ymlの作成
- envファイルの作成
Dockerfileの作成
まず、プロジェクトのルートディレクトリに Dockerfile を作成します。次に、以下のようにコードを記述します。もし yarn を使用している場合は、npm の部分を yarn に置き換えてください。
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "dev"]
まずFROMによって、dockerイメージのベースを指定します。
WORKDIRコマンドはコマンドを実行する作業ディレクトリを指定します。
よって、コンテナ内にappというディレクトリが作成されます。これ以降、appディレクトリ内に色々作成されていきます。
RUNコマンドはDockerイメージのビルド時にシェルコマンドを実行するための命令です。これにより、イメージにソフトウェアをインストールしたり、セットアップの手順を実行したりすることができます。
EXPOSEは、コンテナ内のどのポートを使用するかを決めています。
最後に、CMDはコンテナが起動されたときに実行されるデフォルトのコマンドを指定します。
RUNとCMDの違いについて解説します。
大きな違いは、実行されるタイミングにあります。
RUNコマンドは、Dockerイメージのビルド時に一度だけ実行され、その結果が最終的なイメージに含まれます。例えば、依存パッケージのインストールや設定ファイルの変更などを行います。
CMDコマンドは、コンテナ起動時に実行されます。これは、コンテナが実行される際にデフォルトで実行されるコマンドを指定するもので、例えばアプリケーションの起動やサーバーの開始を行います。
つまり、RUN はイメージ作成時、CMD はコンテナが立ち上がる時に実行されるという違いを覚えておきましょう。
docker-compose.ymlの作成
次に、docker-compose.ymlを作成します。
以下のようにコードを作成します。
version: "3"
services:
app:
build: .
ports:
- "3000:3000"
- "5556:5555"
environment:
- DATABASE_URL=${DATABASE_URL}
depends_on:
- db
volumes:
- .:/app
db:
image: postgres:14
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5433:5432"
volumes:
postgres_data:
この app サービスは1つのコンテナを作成しています。build によって、Dockerfile で定義された内容(ソースコードのコピーや依存パッケージのインストールなど)をもとにコンテナが作成されます。
次に、ports ではホストとコンテナのポートのマッピングが行われており、ローカルPCの localhost からコンテナ内の特定ポートにアクセスできるようにしています。例えば、localhost:3000 へのアクセスが、コンテナ内のポート 3000 にマッピングされています。これにより、ホスト側のブラウザで http://localhost:3000 にアクセスすると、コンテナ内で実行されているアプリケーションが表示されます。
depends_on は、app サービスがデータベース(db)コンテナに依存していることを示しています。これにより、app コンテナが起動する前に db コンテナが起動するように指定されています。
最後に、volumes は、ホストのディレクトリをコンテナ内にマウントする設定です。ここでは、ローカルのプロジェクトディレクトリ(ホスト側の .、つまり現在のディレクトリ)が、コンテナ内の /app にマウントされています。これにより、ホスト側で行ったコードの変更が即座にコンテナ内に反映されるため、開発中にコードを更新した際に再ビルドせずに確認できるようになります。
app:
build: .
ports:
- "3000:3000"
- "5556:5555"
environment:
- DATABASE_URL=${DATABASE_URL}
depends_on:
- db
volumes:
- .:/app
次にdbについて解説します。
まず、environment で環境変数を設定し、データベースの初期設定を行っています。これらの値は .env ファイルに設定してください。
次に、volumes はデータの永続化に関する設定です。もしこの volumes が設定されていないと、コンテナを削除した際にデータも一緒に削除されてしまいます。しかし、volumes を設定しておけば、コンテナの起動や削除に関係なく、データを永続的に保持することが可能です。具体的には、postgres_data というホスト側のストレージ領域を、コンテナ内のデータディレクトリ(/var/lib/postgresql/data)にマウントし、PostgreSQLのデータを保存しています。
portsはappの説明と同じです。ホストのポート5433をコンテナ内のポート5432にマッピングしています。PostgreSQLは通常5432番ポートで動作しますが、ローカル環境で別のPostgreSQLインスタンスが動作している場合でも衝突しないようにホスト側のポートを5433に変更しています。これにより、ホスト側から localhost:5433 でこのデータベースにアクセスできます。
db:
image: postgres:14
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5433:5432"
envファイルの作成
ルートディレクトリに.envファイルを作成します。***のところは自由に設定してください。
POSTGRES_USER=postgres
POSTGRES_PASSWORD=*********
POSTGRES_DB=*********
DATABASE_URL="postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
これでファイルの設定は完了です。
ここからは、ターミナルで実際にコマンドを入力していきます。
docker compose build
次に、作成されたイメージを元にコンテナを作成します。
docker dompose up
これでサーバーは立ち上がりますが、データベース周りはまだ正しく設定されていません。今回はPrismaを使用しているので、Prisma Clientを生成し、マイグレーションを実行する必要があります。
通常、以下のコマンドを使ってPrisma Clientを生成し、マイグレーションを実行します。
npx prisma generate
npx prisma migrate dev --name init
しかし、ターミナルで直接これらのコマンドを実行すると、次のようなエラーが発生するかもしれません。
エラーの原因は、.env ファイルに記述されている DATABASE_URL の一部に「@db:5432」と書いていることです。これは、db という名前のコンテナの5432番ポートにアクセスすることを意味します。つまり、ホスト側(ローカルPC)から直接そのコンテナ内のデータベースにはアクセスできません。
解決策として、ターミナルから直接コンテナ内に入ってコマンドを実行します。コンテナ同士(app と db)は通信できるので、この方法を使うことで問題を解決します。
まず、以下のコマンドで app のコンテナ内に入ります。コンテナIDは docker ps コマンドを実行することで確認できます。この際、db のコンテナIDではなく、app のコンテナIDを使用してください。
docker exec -it コンテナID sh
これで、コンテナ内に入ることができました。
次に、以下のコマンドを実行して、マイグレーションを行います。
npx prisma generate
npx prisma migrate dev --name init
これで、エラーが発生せずにマイグレーションが完了するはずです。
データベース周りの構築もこれで完了しました。アプリケーションとデータベースが正しく連携できるようになります。
終わりに
今回は、初めてDockerを使用してNext.jsとデータベースの環境を構築してみました。
ここまで読んでいただき、ありがとうございました!