2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docker/コンテナ仮想環境Advent Calendar 2024

Day 17

【Docker Compose】コンテナで起動した PostgreSQL に接続する

Posted at

はじめに

こんにちは、梅雨です。

バックエンドの開発において、環境の統一やコンテナの隔離などの目的から 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 を採用しました。バックエンドに採用する言語やフレームワークには特に制限はないため、お使いのバックエンドがある場合はそちらをお使いいただいて大丈夫です。

backend/package.json
{
  "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"
  }
}
backend/tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}
backend/src/server.ts
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");
});
backend/Dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
CMD [ "npm", "run", "dev" ]

docker-compose.yaml の記述

続いて、docker-compose.yaml を記述していきます。

サービスにはバックエンドとデータベースの二つを定義します。サービス名は何でも大丈夫なので、好きな名前を使用してください。

docker-compose.yaml
services:
  backend:
  db:

次に、バックエンドのサービスの中身を書いていきます。

build には Dockerfile のあるパスを記述します。

volumes にはコンテナにマウントするパスを記述します。今回の例では、ホスト側の ./backend をコンテナ側の /app にマウントしています。

ports にはホスト側のポートとコンテナ側のポートのマッピング関係を指定します。今回の例では Express のサーバを 8000 番ポートで起動するようにしているため、8000:8000 としています。

docker-compose.yaml
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つの名前は何でも大丈夫ですが、後でデータベースに接続する時に使用するので、わかりやすい名前にすることをお勧めします。

docker-compose.yaml
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 のデータの保存先のパスです。

docker-compose.yaml
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 に接続するためのインスタンスを作成します。

backend/src/utils/sql.ts
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 を用いて開発を行う際は、ぜひ参考にしてみてください。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?