Docker+PostgreSQL+Go+Airで実現する快適サーバーサイド開発環境構築のすゝめ

# はじめに

先日、研究室の後輩たちと[技育CAMPハッカソン](https://talent.supporterz.jp/events/6f769c37-5709-4ddc-804e-4c4ff38d2112/)に参加してきました。
ハッカソンで何を開発したか等は、後輩が記事にまとめてくれたので、良ければ以下の記事をご覧ください。

https://qiita.com/aridome222/items/daf4465faffa608cb202

この記事では、Go言語の経験者がほとんどいない中、バックエンドの開発言語にGoを採用した今回のハッカソンで、入門Gopherのためにも快適な開発ができるよう環境構築をしたので、その紹介をさせていただきます。

紹介するにあたって、Goでのサーバーサイド開発に使えるテンプレートリポジトリも作成しました。Goを使ってみたいけど環境構築はめんどくさい方や、ハッカソン等でパッと使えるGoのテンプレートリポジトリを探している方は、使ってみてください。

# リポジトリの説明

GO + PostgreSQL での開発を、Docker と Air により快適なものとするためのテンプレートリポジトリです。
Go で開発する API サーバー用のコンテナと、PostgreSQL による DB 用のコンテナ、DB に対してGUIで確認・操作を行えるPGAdmin用のコンテナの3つを Docker Compose により立ち上げます。また、Air によるホットリロードのおかげで、Go のソースコードに対する変更は、立ち上げている API サーバー用のコンテナに反映されます。
以下が作成したリポジトリです。

https://github.com/KakinokiKanta/go-dev-template

## 手元で動かしてみよう！

1つ1つのファイルの説明よりも、動きを見た方がわかりやすいかと思いますので、さっそく動かし方を紹介します！
細かいファイルの説明などは後半で記載していますので、気になる方はご覧ください。

### 環境構築

1. [Docker](https://www.docker.com/ja-jp/get-started/)をインストール
2. 本リポジトリをクローンするか、テンプレートとして選択してリポジトリを作成
3. `docker network create api-db-network`
4. `docker network create db-pgadmin-network`
5. `docker compose build`

Dockerのビルドに成功すると下記のような出力が得られます。

```sh
> docker compose build
2024/09/25 13:34:29 http2: server: error reading preface from client //./pipe/docker_engine: file has already been closed
[+] Building 0.0s (0/0)  docker:default
[+] Building 18.9s (10/10) FINISHED                                                                                                                                                                                       docker:default
 => [api internal] load build definition from go.dockerfile                                                                                                                                                                         0.0s
 => => transferring dockerfile: 239B                                                                                                                                                                                                0.0s 
 => [api internal] load metadata for docker.io/library/golang:1.23.1-alpine3.20                                                                                                                                                     1.5s 
 => [api internal] load .dockerignore                                                                                                                                                                                               0.0s
 => => transferring context: 2B                                                                                                                                                                                                     0.0s 
 => [api internal] load build context                                                                                                                                                                                               0.0s 
 => => transferring context: 149B                                                                                                                                                                                                   0.0s 
 => [api 1/5] FROM docker.io/library/golang:1.23.1-alpine3.20@sha256:ac67716dd016429be8d4c2c53a248d7bcdf06d34127d3dc451bda6aa5a87bc06                                                                                               5.7s 
 => => resolve docker.io/library/golang:1.23.1-alpine3.20@sha256:ac67716dd016429be8d4c2c53a248d7bcdf06d34127d3dc451bda6aa5a87bc06                                                                                                   0.0s 
 => => sha256:ac67716dd016429be8d4c2c53a248d7bcdf06d34127d3dc451bda6aa5a87bc06 10.29kB / 10.29kB                                                                                                                                    0.0s 
 => => sha256:3058c543b93017c20123f4ffe8ca779c88ade0102361ab9a290bf7d590360fc4 1.92kB / 1.92kB                                                                                                                                      0.0s 
 => => sha256:b5ada884192f173018fcb39688bd70545669ab105941231067ea5dbed4ac6914 2.07kB / 2.07kB                                                                                                                                      0.0s 
 => => sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170 3.62MB / 3.62MB                                                                                                                                      0.6s 
 => => sha256:ab19dfae90efdd651c37b568f750d129c6d7c47d3a902982eefcd7c6567ccd5d 290.88kB / 290.88kB                                                                                                                                  0.3s 
 => => sha256:e7bff916ab0c126c9d943f0c481a905f402e00f206a89248f257ef90beaabbd8 74.00MB / 74.00MB                                                                                                                                    2.9s 
 => => sha256:78cee99375e3e0bb16a7bcb00218932b90225708c1a53e97b98b5230fe87b86a 125B / 125B                                                                                                                                          0.7s 
 => => extracting sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170                                                                                                                                           0.1s 
 => => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B                                                                                                                                            0.8s 
 => => extracting sha256:ab19dfae90efdd651c37b568f750d129c6d7c47d3a902982eefcd7c6567ccd5d                                                                                                                                           0.1s 
 => => extracting sha256:e7bff916ab0c126c9d943f0c481a905f402e00f206a89248f257ef90beaabbd8                                                                                                                                           2.6s 
 => => extracting sha256:78cee99375e3e0bb16a7bcb00218932b90225708c1a53e97b98b5230fe87b86a                                                                                                                                           0.0s 
 => => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1                                                                                                                                           0.0s 
 => [api 2/5] COPY ./app /go/app/                                                                                                                                                                                                   0.2s 
 => [api 3/5] WORKDIR /go/app/                                                                                                                                                                                                      0.0s 
 => [api 4/5] RUN go mod download && go mod tidy                                                                                                                                                                                    4.5s 
 => [api 5/5] RUN go install github.com/air-verse/air@latest                                                                                                                                                                        6.4s 
 => [api] exporting to image                                                                                                                                                                                                        0.5s 
 => => exporting layers                                                                                                                                                                                                             0.5s 
 => => writing image sha256:920413d3115f1a2d59f591588e3b0e7531a80f92a57f4f17f7553433e37e54e0                                                                                                                                        0.0s 
 => => naming to docker.io/library/go-dev-template-api
```

### 実行方法

下記コマンドを実行し、Docker Composeを実行します。
```
docker compose up
```

実行時のログを確認すると、Airが起動していることや、テーブルの作成やテストデータの挿入、APIサーバーからデータベースの接続にも成功していることが確認できます！
```sh
> docker compose up     
[+] Running 2/0
 ✔ Volume "go-dev-template_pgadmin-data"  Created                                                                                                                                                                                   0.0s 
 ✔ Volume "go-dev-template_db"            Created                                                                                                                                                                                   0.0s 
 - Container db                           Created                                                                                                                                                                                   0.1s 
 - Container api                          Created                                                                                                                                                                                   0.0s 
 - Container pgadmin4                     Created                                                                                                                                                                                   0.0s 
Attaching to api, db, pgadmin4
db        | The files belonging to this database system will be owned by user "postgres".
db        | This user must also own the server process.
db        |
db        | The database cluster will be initialized with locale "en_US.utf8".
db        | The default database encoding has accordingly been set to "UTF8".
db        | The default text search configuration will be set to "english".
db        |
db        | Data page checksums are disabled.
db        |
db        | fixing permissions on existing directory /var/lib/postgresql/data ... ok
db        | creating subdirectories ... ok
db        | selecting dynamic shared memory implementation ... posix
db        | selecting default max_connections ... 100
db        | selecting default shared_buffers ... 128MB
db        | selecting default time zone ... Etc/UTC
db        | creating configuration files ... ok
db        | running bootstrap script ... ok
api       |
api       |   __    _   ___
api       |  / /\  | | | |_)
api       | /_/--\ |_| |_| \_ v1.60.0, built with Go go1.23.1
api       |
api       | watching .
api       | !exclude tmp
api       | building...
db        | performing post-bootstrap initialization ... ok
db        | syncing data to disk ... ok
db        |
db        |
db        | Success. You can now start the database server using:
db        |
db        |     pg_ctl -D /var/lib/postgresql/data -l logfile start
db        |
db        | initdb: warning: enabling "trust" authentication for local connections
db        | initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
db        | waiting for server to start....2024-09-25 07:21:19.690 UTC [48] LOG:  starting PostgreSQL 16.4 (Debian 16.4-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
db        | 2024-09-25 07:21:19.693 UTC [48] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db        | 2024-09-25 07:21:19.703 UTC [51] LOG:  database system was shut down at 2024-09-25 07:21:19 UTC
db        | 2024-09-25 07:21:19.707 UTC [48] LOG:  database system is ready to accept connections
db        |  done
db        | server started
db        | CREATE DATABASE
db        |
db        |
db        | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sql
db        | CREATE TABLE
db        | INSERT 0 1
db        | INSERT 0 1
db        | INSERT 0 1
db        | INSERT 0 1
db        | INSERT 0 1
db        |
db        |
db        | waiting for server to shut down...2024-09-25 07:21:20.019 UTC [48] LOG:  received fast shutdown request
db        | .2024-09-25 07:21:20.025 UTC [48] LOG:  aborting any active transactions
db        | 2024-09-25 07:21:20.026 UTC [48] LOG:  background worker "logical replication launcher" (PID 54) exited with exit code 1
db        | 2024-09-25 07:21:20.029 UTC [49] LOG:  shutting down
db        | 2024-09-25 07:21:20.033 UTC [49] LOG:  checkpoint starting: shutdown immediate
db        | 2024-09-25 07:21:20.157 UTC [49] LOG:  checkpoint complete: wrote 931 buffers (5.7%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.022 s, sync=0.082 s, total=0.128 s; sync files=304, longest=0.004 s, average=0.001 s; distance=4270 kB, estimate=4270 kB; lsn=0/1915BB0, redo lsn=0/1915BB0
db        | 2024-09-25 07:21:20.162 UTC [48] LOG:  database system is shut down
db        |  done
db        | server stopped
db        |
db        | PostgreSQL init process complete; ready for start up.
db        |
db        | 2024-09-25 07:21:20.233 UTC [1] LOG:  starting PostgreSQL 16.4 (Debian 16.4-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
db        | 2024-09-25 07:21:20.233 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
db        | 2024-09-25 07:21:20.233 UTC [1] LOG:  listening on IPv6 address "::", port 5432
db        | 2024-09-25 07:21:20.236 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db        | 2024-09-25 07:21:20.240 UTC [66] LOG:  database system was shut down at 2024-09-25 07:21:20 UTC
db        | 2024-09-25 07:21:20.244 UTC [1] LOG:  database system is ready to accept connections
api       | running...
api       | データベース接続成功
api       | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
api       |
api       | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
api       |  - using env:       export GIN_MODE=release
api       |  - using code:      gin.SetMode(gin.ReleaseMode)
api       |
api       | [GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
api       | [GIN-debug] GET    /get                      --> main.main.func2 (3 handlers)
api       | [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
api       | Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
api       | [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
api       | [GIN-debug] Listening and serving HTTP on :8080
pgadmin4  | NOTE: Configuring authentication for DESKTOP mode.
pgadmin4  | pgAdmin 4 - Application Initialisation
pgadmin4  | ======================================
pgadmin4  |
pgadmin4  | ----------
pgadmin4  | Loading servers with:
pgadmin4  | User: pgadmin4@pgadmin.org
pgadmin4  | SQLite pgAdmin config: /var/lib/pgadmin/pgadmin4.db
pgadmin4  | ----------
pgadmin4  | Added 0 Server Group(s) and 1 Server(s).
pgadmin4  | postfix/postlog: starting the Postfix mail system
pgadmin4  | [2024-09-25 07:21:33 +0000] [1] [INFO] Starting gunicorn 20.1.0
pgadmin4  | [2024-09-25 07:21:33 +0000] [1] [INFO] Listening at: http://[::]:80 (1)
pgadmin4  | [2024-09-25 07:21:33 +0000] [1] [INFO] Using worker: gthread
pgadmin4  | [2024-09-25 07:21:33 +0000] [121] [INFO] Booting worker with pid: 121
```

`http://localhost:8080/get`にアクセスしてみると、以下のレスポンスを得られ、データベースに挿入したテストデータが取得できます。

```json
{
  "id": "1",
  "name": "a1_name",
  "password": "a1_password"
}
```

`http://localhost:81`にアクセスしてみると、PGAdminでDocker Compose実行時に挿入したテストデータを確認することができます。上記で取得したデータがデータベース内にも存在することが確認できますね。

![スクリーンショット 2024-09-25 162235.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3589814/f8db5d93-a723-031a-3b54-187adad0cdf0.png)

## リポジトリの説明

一応、ほぼすべてのディレクトリやファイルについて説明していますが、冗長な説明部分も多いため、実際にリポジトリをクローンして手元で触ってみて、よくわからない部分だけ説明を読む形を推奨します。

```
go-dev-template/
├── app/ # Go関連のファイルを置くディレクトリ
│   ├── tmp/      # Airのホットリロードのために自動生成される
│   ├── .air.toml # Airの設定ファイル
│   ├── go.mod    # Goで使うモジュールの依存関係を管理するファイル
│   ├── go.sum    # go.modの整合性を担保するためのファイル
│   └── main.go   # APIサーバー起動時のエントリーポイントとなるファイル
├── config/
│   └── servers.json # PGAdminのための設定ファイル
├── sql/
│   └── init.sql       # Docker起動時に実行するSQLファイル
├── docker-compose.yml # APIサーバー、DB、PGAdminの3つのコンテナに関する設定ファイル
└── go.dockerfile      # GoのAPIサーバー用のDocker設定ファイル
```

#### docker-compose.yml

Goで実装するAPIサーバー、PostgreSQLのデータベース、PGAdminの3つのDockerコンテナを立ち上げるための設定ファイルです。
3つのコンテナを立ち上げた上で、各コンテナを接続するために、dockerネットワークを生成・設定してあげる必要があります。

```
docker network create api-db-network
```
```
docker network create db-pgadmin-network
```

今回のハッカソンでは、バックエンド開発の経験がほとんどないメンバーもいたため、できるだけコマンドラインでデータベースの確認などをしなくても良いようにしたいと考え、PGAdminというGUIでデータベースに対する操作を行えるツールを導入しました。

```yml
services:
  api:
    container_name: api
    build:
      context: .
      dockerfile: go.dockerfile
    ports:
      - 8080:8080
    depends_on:
      - db
    tty: true
    volumes:
      - ./app:/go/app
    networks:
      - api-db-network

  db:
    container_name: db
    image: postgres:16
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: db-user
      POSTGRES_PASSWORD: db-password
      POSTGRES_DB: db-name
    volumes:
      - db:/var/lib/postgresql/data
      - ./sql:/docker-entrypoint-initdb.d
    networks:
      - api-db-network
      - db-pgadmin-network

  pgadmin4:
    container_name: pgadmin4
    image: dpage/pgadmin4:8.4
    ports:
      - 81:80
    volumes:
      - pgadmin-data:/var/lib/pgadmin
      - ./config/servers.json:/pgadmin4/servers.json
    environment:
      PGADMIN_DEFAULT_EMAIL: user@example.com
      PGADMIN_DEFAULT_PASSWORD: password
      PGADMIN_CONFIG_SERVER_MODE: "False"
      PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
    depends_on:
      - db
    networks:
      - db-pgadmin-network

volumes:
  db:
  pgadmin-data:

networks:
  api-db-network:
    external: true
  db-pgadmin-network:
    external: true
```

#### go.dockerfile

GoのAPIサーバー用のDocker設定ファイルです。
app/をgo/app/にコピーして、作業用のディレクトリとしています。また、新たにgoのサードパーティ製パッケージを導入したい場合には、通常通りapp/下で`go install ~`もしくは`go get ~`とすれば、go.modファイルに追加されるため、Dockerコンテナ起動時に必要なパッケージすべてがインストールされます。

```dockerfile
FROM golang:1.23.1-alpine3.20

COPY ./app /go/app/

WORKDIR /go/app/

RUN go mod download && go mod tidy
RUN go install github.com/air-verse/air@latest

CMD ["air", "-c", ".air.toml"]

EXPOSE 8080
```

### app/

Goで実装するファイルはこのディレクトリ下に置いていきます。
ルートディレクトリに直接main.goをおいても良かったのですが、今後マイグレーション用で別モジュールを作りたくなった際などに便利だと考えたため、appディレクト下にまとめています。

#### tmp/

Airでホットリロードを有効にするため、Goファイルをビルドした実行ファイルなどが自動生成されます。

#### .air.toml

Airの設定を記述しています。
`air init`で生成したファイルとほぼ同じです。

```toml
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  bin = "tmp/main.exe"
  cmd = "go build -o ./tmp/main.exe ."
  delay = 0
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  include_file = []
  kill_delay = "0s"
  log = "build-errors.log"
  poll = true
  poll_interval = 0
  post_cmd = []
  pre_cmd = []
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_error = false

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  main_only = false
  time = false

[misc]
  clean_on_exit = false

[proxy]
  app_port = 0
  enabled = false
  proxy_port = 0

[screen]
  clear_on_rebuild = false
  keep_scroll = true

```

#### main.go

基本的な処理しか記述していないため、詳細な説明は省略します。
データベースとしてはPostgreSQLを採用し、フレームワークにはGinを採用しています。
データベース接続に成功した上で、データベースからレコードを取得できることを確認するために、/getエンドポイントを用意しています。このエンドポイントを叩くとaccountsテーブルからレコードを1行取得し、レスポンスとして返します。

:::note warn
実際にこのリポジトリをテンプレートリポジトリとして使う場合には、データベース情報は環境変数として.envファイルなどに記載するようにしてください。
:::


```go
package main

import (
	"database/sql"
	"fmt"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	_ "github.com/lib/pq"
)

// データベースからレコード取得する際に用いる変数
var (
	id int
	name string
	password string
)

func main() {
	// データベース接続
	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
		"db", "db-user", "db-password", "db-name", "5432")
	db, err := sql.Open("postgres", dsn)
	if err != nil {
		panic("データベース接続失敗")
	} else {
		fmt.Println("データベース接続成功")
	}

	r := gin.Default()

    // GET /get
	r.GET("/get", func(ctx *gin.Context) {
		row := db.QueryRow("SELECT id, name, password FROM accounts;")
		if row.Err() != nil {
			ctx.JSON(http.StatusInternalServerError, gin.H{"error": row.Err().Error()})
			return
		}
		err := row.Scan(&id, &name, &password)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, gin.H{"error": err})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"id": strconv.Itoa(id),
			"name": name,
			"password": password,
		})
	})
	r.Run()
}

```

### configディレクトリ

#### servers.jsonファイル

このファイルには、データベースに関する設定を記述することで、PGAdminでのログイン作業などを削減しています。

```json
{
  "Servers": {
    "1": {
      "Name": "db-name",
      "Group": "Servers",
      "Host": "db",
      "Port": 5432,
      "MaintenanceDB": "postgres",
      "Username": "db-user",
      "SSLMode": "prefer"
    }
  }
}
```

### sqlディレクトリ

#### init.sqlファイル

Docker Compose実行時に、テーブルおよびテストデータを挿入するためのSQLファイルです。
accountsテーブルを作成し、4件のテストデータを挿入します。

```sql
CREATE TABLE accounts (
  id SERIAL NOT NULL,
  name VARCHAR(20) NOT NULL,
  password VARCHAR(50) NOT NULL,
  PRIMARY KEY (id)
);

INSERT INTO accounts (name, password) VALUES ('a1_name', 'a1_password');
INSERT INTO accounts (name, password) VALUES ('a2_name', 'a2_password');
INSERT INTO accounts (name, password) VALUES ('a3_name', 'a3_password');
INSERT INTO accounts (name, password) VALUES ('a4_name', 'a4_password');
```

# トラブルシューティング

私の手元で環境構築する際に起こったエラーについて、解決策を記載しておきます。

### Dockerのビルドに失敗する

```sh
> docker compose build
2024/09/25 13:29:24 http2: server: error reading preface from client //./pipe/docker_engine: file has already been closed
[+] Building 1.5s (3/3) FINISHED                                                                                                                                                                    docker:default
 => [api internal] load build definition from go.dockerfile                                                                                                                                                   0.0s
 => => transferring dockerfile: 232B                                                                                                                                                                          0.0s 
 => ERROR [api internal] load metadata for docker.io/library/1.23.1-alpine3.20:latest                                                                                                                         1.5s 
 => [api auth] library/1.23.1-alpine3.20:pull token for registry-1.docker.io                                                                                                                                  0.0s
------
 > [api internal] load metadata for docker.io/library/1.23.1-alpine3.20:latest:
------
failed to solve: 1.23.1-alpine3.20: failed to resolve source metadata for docker.io/library/1.23.1-alpine3.20:latest: failed to authorize: failed to fetch oauth token: unexpected status from GET request to https://auth.docker.io/token?scope=repository%3Alibrary%2F1.23.1-alpine3.20%3Apull&service=registry.docker.io: 401 Unauthorized
```

対処する上で、こちらの記事が参考になりました。

https://medium.com/@matijazib/how-to-fix-the-error-load-metadata-for-docker-io-when-building-your-docker-image-on-macos-ec6deee664fd

Docker構成ファイルを削除

```
rm ~/.docker/config.json
```

Dockerキャッシュを削除

```
rm  ~/.docker/buildx
```

Dockerログアウト

```
docker logout
```

Dockerログイン

```
docker login
```

### Dockerコンテナの立ち上げに失敗する

```sh
> docker compose up   
[+] Running 2/0
 ✔ Volume "go-dev-template_db"            Created                                                                                                                                                                                   0.0s 
 ✔ Volume "go-dev-template_pgadmin-data"  Created                                                                                                                                                                                   0.0s 
 - Container db                           Creating                                                                                                                                                                                  0.0s 
Error response from daemon: Conflict. The container name "/db" is already in use by container "db0b3ae760659a491f77e4a91c38128804931d6618b836cb9ac6f5547b22a0a7". You have to remove (or rename) that container to be able to reuse that name.
```

以下の記事を参考に解決しました。

https://qiita.com/suzu12/items/069f4517bd9a0d2ac704

Dockerコンテナのリストを確認
```sh
> docker ps -a
CONTAINER ID   IMAGE                                        COMMAND                   CREATED        STATUS                      PORTS     NAMES
db0b3ae76065   postgres:16                                  "docker-entrypoint.s…"   2 weeks ago    Created                               db
1f81507429d9   mybrary-backend-api                          "air -c .air.toml"        3 weeks ago    Exited (1) 3 weeks ago                api
```

名前がコンフリクトを起こしていると言われたDockerコンテナを削除
```sh
> docker rm db0b3ae76065
db0b3ae76065
> docker rm 1f81507429d9
1f81507429d9
```

# おわりに
まだまだGopherとなって日が浅いため、改善する余地があると思いますので、もっとこうした方が良いなどの意見がありましたら、コメントしていただけると嬉しいです。