はじめに
こんにちは、梅雨です。
バックエンドの開発において、環境の統一やコンテナの隔離などの目的から Docker を採用する事例は多いと思います。
Docker を使用する場合、通常の開発体験とは少し異なるため、あまり慣れていない方だと開発に困ってしまうこともあるでしょう。
そんな方に向けて、この記事では Docker コンテナ内で起動した PostgreSQL のデータベースに接続する方法について解説していきます。
Docker 環境構築
アプリケーションの構成は以下のようになっています。
docker-psql-demo
├── backend
│ ├── Dockerfile
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── server.ts
│ └── tsconfig.json
└── docker-compose.yaml
バックエンドの各ファイル
今回は、バックエンドに Node.js のフレームワークである Express を採用しました。バックエンドに採用する言語やフレームワークには特に制限はないため、お使いのバックエンドがある場合はそちらをお使いいただいて大丈夫です。
{
"name": "backend",
"scripts": {
"dev": "nodemon ./src/server.ts"
},
"dependencies": {
"express": "^4.21.2"
},
"devDependencies": {
"@types/express": "^5.0.0",
"nodemon": "^3.1.9",
"ts-node": "^10.9.2"
}
}
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(8000, () => {
console.log("Server is running on http://localhost:8000");
});
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
CMD [ "npm", "run", "dev" ]
docker-compose.yaml
の記述
続いて、docker-compose.yaml
を記述していきます。
サービスにはバックエンドとデータベースの二つを定義します。サービス名は何でも大丈夫なので、好きな名前を使用してください。
services:
backend:
db:
次に、バックエンドのサービスの中身を書いていきます。
build
には Dockerfile
のあるパスを記述します。
volumes
にはコンテナにマウントするパスを記述します。今回の例では、ホスト側の ./backend
をコンテナ側の /app
にマウントしています。
ports
にはホスト側のポートとコンテナ側のポートのマッピング関係を指定します。今回の例では Express のサーバを 8000 番ポートで起動するようにしているため、8000:8000
としています。
services:
backend:
+ build: ./backend
+ volumes:
+ - ./backend:/app
+ ports:
+ - 8000:8000
db:
次に、データベースのサービスの中身を書いていきます。
image
にはビルドイメージを記述します。バックエンドでは自分で記述した Dockerfile を指定しましたが、データベースでは DockerHub から直接ビルドイメージを使用します。
ports
には先程と同様にマッピングしたポート番号を記述します。5432 は PostgreSQL のデフォルトのポート番号です。
environment
にはコンテナに渡す環境変数を記述します。POSTGRES_DB
にはデータベース名、POSTGRES_USER
にはユーザ名、POSTGRES_PASSWORD
にはパスワードを指定します。これら3つの名前は何でも大丈夫ですが、後でデータベースに接続する時に使用するので、わかりやすい名前にすることをお勧めします。
services:
backend:
build: ./backend
volumes:
- ./backend:/app
ports:
- 8000:8000
db:
+ image: postgres:16-alpine
+ ports:
+ - 5432:5432
+ environment:
+ POSTGRES_DB: docker_psql_demo
+ POSTGRES_USER: admin
+ POSTGRES_PASSWORD: postgres
最後に、このままだとコンテナを終了するたびにデータが揮発してしまうため、永続化用のマウントを記述します。
ボリューム名はサービス名と同じ db
を指定していますが、これは別に同じでなくてもいいです。各自わかりやすい名前を使用してください。
volumes
ではdb
ボリュームをコンテナ側の /var/lib/postgresql/data
にマウントしています。この /var/lib/postgresql/data
は PostgreSQL のデータの保存先のパスです。
services:
backend:
build: ./backend
volumes:
- ./backend:/app
ports:
- 8000:8000
db:
image: postgres:16-alpine
+ volumes:
+ - db:/var/lib/postgresql/data
ports:
- 5432:5432
environment:
POSTGRES_DB: docker_psql_demo
POSTGRES_USER: admin
POSTGRES_PASSWORD: postgres
+ volumes:
+ db:
以上で環境構築の準備ができました。docker compose build
でコンテナをビルドしましょう。
$ docker compose build
ターミナルからの接続
まずはコンテナを起動します。-d
オプションをつけることで、バックグラウンドでコンテナの起動が可能です。
$ docker compose up -d
続いて、db コンテナに入り bash を起動しましょう。
$ docker compose exec db bash
postgres イメージにはデフォルトで bash がインストールされています。
bash が起動できたら、まずは psql
コマンドを実行してみましょう。引数には先程 docker-compose.yaml
内で環境変数として渡したデータベース名を指定します。
\# psql docker_psql_demo
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: role "root" does not exist
すると、"root" ロールがないと怒られてしまいます。
PostgreSQL はデフォルトで "root" というユーザを使用するため、先程環境変数として渡したユーザ名を -U
オプションで指定しましょう。
/# psql docker_psql_demo -U admin
psql (16.6)
Type "help" for help.
docker_psql_demo=\#
これでターミナルから PostgreSQL のデータベースに接続ができました。
せっかくなので、このままテーブルを作成してしまいましょう。
docker_psql_demo=\# CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, age INTEGER);
CREATE TABLE
現在あるテーブルは \dt
コマンドで確認できます。
docker_psql_demo=\# \dt;
List of relations
Schema | Name | Type | Owner
--------+-------+-------+-------
public | users | table | admin
(1 row)
アプリケーションからの接続
今度はバックエンドのアプリケーションからデータベースに接続する方法を解説します。
まずは npm の postgres
パッケージをインストールします。
自身のバックエンドがある方は飛ばしていただいて大丈夫です。
$ docker compose run --rm backend npm i postgres
続いて backend/src/utils/sql.ts
で PostgreSQL に接続するためのインスタンスを作成します。
import postgres from "postgres";
const sql = postgres("postgres://admin:postgres@db:5432/docker_psql_demo");
export default sql;
ここで重要となってくるのが、postgres://
から始まる URI です。
この URI は以下のページで詳細に説明されています。
基本的な構造は
postgres:// ユーザ名
: パスワード
@ ホスト名
: ポート番号
/ テーブル名
のようになっており、今回の例では
- ユーザ名: admin
- パスワード: postgres
- ホスト名: db
- ポート番号: 5432
- テーブル名: docker_psql_demo
となっています。ホスト名を localhost
ではなくサービス名の db
とすることに注意してください。
インスタンスの準備ができたら、/users
の CRUD 操作を行えるルーティングを記述してみましょう。
// Read users
app.get("/users", async (req, res) => {
const users = await sql`SELECT * FROM users`;
res.send(users);
});
// Read user
app.get("/users/:id", async (req, res) => {
const { id } = req.params;
const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
if (!user) {
res.sendStatus(404);
}
res.send(user);
});
// Create user
app.post("/users", async (req, res) => {
const { name, age } = req.body;
if (!name || !age) {
res.sendStatus(400);
}
const [user] =
await sql`INSERT INTO users (name, age) VALUES (${name}, ${age}) RETURNING id`;
res.setHeader("Location", `http://localhost:8000/${user.id}`).sendStatus(201);
});
// Update user
app.patch("/users/:id", async (req, res) => {
const { id } = req.params;
const { name, age } = req.body;
if (!name || !age) {
res.sendStatus(400);
}
const [user] =
await sql`UPDATE users SET name = ${name}, age = ${age} WHERE id = ${id} RETURNING id`;
if (!user) {
res.sendStatus(404);
}
res.sendStatus(204);
});
// Delete user
app.delete("/users/:id", async (req, res) => {
const { id } = req.params;
const [user] = await sql`DELETE FROM users WHERE id = ${id} RETURNING id`;
if (!user) {
res.sendStatus(404);
}
res.sendStatus(204);
});
curl コマンドを使って確認します。
Get users
$ curl localhost:8000/users -X GET
[]
Create user
$ curl localhost:8000/users -X POST -H 'Content-Type:application/json' -d '{"name":"meiyu","age":23}'
Created
Get user
$ curl localhost:8000/users/1 -X GET
{"id":1,"name":"meiyu","age":23}
$ curl localhost:8000/users/2 -X GET
Not Found
Update user
$ curl localhost:8000/users/1 -X PATCH -H 'Content-Type:application/json' -d '{"name":"tsuyuni","age":23}'
$ curl localhost:8000/users/2 -X PATCH -H 'Content-Type:application/json' -d '{"name":"tsuyuni","age":23}'
Not Found
Delete User
$ curl localhost:8000/users/1 -X DELETE
$ curl localhost:8000/users/2 -X DELETE
Not Found
以上が Docker コンテナで起動した PostgreSQL のデータベースにアプリケーションから接続する方法となります。
おわりに
今回は Docker コンテナ内で起動した PostgreSQL のデータベースに、ターミナルとアプリケーションのそれぞれから接続する方法について解説しました。
Docker と PostgreSQL を用いて開発を行う際は、ぜひ参考にしてみてください。