Golang / React / PostgreSQLのホットリロードできる開発環境をDockerで作ってみた

#構成
- バックエンド: Golang
- フロントエンド: React(TypeScript) + Tailwind CSS
- DB: PostgreSQL

#なぜ作ろうと思ったのか
開発中に、バックエンド、フロントエンド、データベースを起動、再起動の管理を各々するのが億劫になったので、どうせならdockerで統一してしまおうというのが作ろうとしたのがきっかけです。

#docker-compose
```
version: "3.7"

services:
  front:
    container_name: front
    build:
      context: ./front
      dockerfile: Dockerfile
    ports:
      - 3000:3000
    working_dir: /src
    volumes:
      - ./front:/src
    env_file:
      - ./front/.env
    tty: true
    stdin_open: true

  api:
    container_name: api
    build:
      context: ./api
      dockerfile: Dockerfile
      target: build
    volumes:
      - ./api/:/go/app
    env_file:
      - ./api/.env
    command: "air -c .air.toml"
    ports:
      - "8080:8080"
      - "2345:2345"
    privileged: true
    security_opt:
      - apparmor:unconfined
    cap_add:
      - SYS_PTRACE
    tty:
      true
    depends_on:
      - db

  db:
    container_name: db
    build:
      context: ./docker/postgres
      dockerfile: Dockerfile
    ports:
      - '5432:5432'
    expose:
      - 5432
    environment:
      POSTGRES_DB: api
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - db:/var/lib/postgresql/data

volumes:
  db:
    driver: local
```

# Dockerfile(バックエンド)
dlvとairは必須。あとは開発効率上げるためのツールとか

```
# Build Step
FROM golang:1.16-alpine3.14 AS build

WORKDIR /go/app/

ARG build
ARG version
ARG CGO_ENABLED=0
ARG GOOS=linux
ARG GOARCH=amd64

# いらないかも
COPY go.mod go.sum ./
RUN go mod download

RUN set -eux && \
  apk update && \
  apk add --no-cache git curl make alpine-sdk build-base && \
  go install github.com/go-delve/delve/cmd/dlv@latest && \
  go install github.com/rubenv/sql-migrate/...@latest && \
  go install github.com/swaggo/swag/cmd/swag@latest && \
  go install github.com/golang/mock/mockgen@latest && \
  curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin && \
  curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin

COPY . .

RUN set -eux && \
  go build -gcflags "all=-N -l" -o api ./main.go

# Server Step
FROM alpine3.14
ENV TZ=Asia/Tokyo

WORKDIR /app

COPY --from=build /go/app/api .

RUN set -x && \
  addgroup go && \
  adduser -D -G go go && \
  chown -R go:go /app/api

CMD ["./api"]
```
## Go1.16からgo installが使えるようになったらしいです。
こちらの記事を参考にさせていただきました。
go mod tidyとかするとツール群が消えてしまうので、go install 便利です。
https://qiita.com/eihigh/items/9fe52804610a8c4b7e41

# Dockerfile(フロントエンド)
```
FROM node:14.17.0-alpine

ENV NODE_ENV=development
ENV LANG ja_JP.UTF-8
ENV TZ Asia/TOKYO

RUN apk add --update --no-cache \
            curl \
            git \
            bash

WORKDIR /src
COPY ["package.json", "yarn.lock", "./"]
RUN yarn install
COPY . .
ENTRYPOINT [ "yarn", "start" ]
```

# Dockerfile(データベース)
```
FROM postgres:10.17
RUN localedef -i ja_JP -c -f UTF-8 -A /usr/share/locale/locale.alias ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
COPY init.sql /docker-entrypoint-initdb.d/init.sql
RUN chmod 0755 /docker-entrypoint-initdb.d/init.sql
```

## init.sql
`docker/postgres/Dockerfile`
DB作成時に実行したいSQLとかを入れる。こちらは任意

# golangのホットリロード対応

## .air.tomlを追加
```
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o /tmp/api ./main.go"
# Binary file yields from `cmd`.
bin = "/tmp/api"
# Customize binary.
# デバッグする場合
# full_bin = "APP_ENV=dev APP_USER=air /go/bin/dlv exec /tmp/api --headless=true --listen=:2345 --api-version=2 --accept-multiclient"
# 通常実行の場合
full_bin = "APP_ENV=dev APP_USER=air /tmp/api"

# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "front"]
# Watch these directories if you specified.
include_dir = []
# Exclude files.
exclude_file = []
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms

[log]
# Show log time
time = false

[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# Delete tmp directory on exit
clean_on_exit = true
```

## GolandやVScodeのデバッグモード
以下を切り替える必要がある。これは、なんとか自動化したい...

### デバッグする場合
`full_bin = "APP_ENV=dev APP_USER=air /go/bin/dlv exec /tmp/api --headless=true --listen=:2345 --api-version=2 --accept-multiclient"`

### 通常実行の場合
`full_bin = "APP_ENV=dev APP_USER=air /tmp/api"`


#残課題
以下は今後なんとか解決したいなと思ってます。

- デバッグモードの切り替えを手動でする必要があるのでその解消
- docker内でmake testを実行するとエラーが発生する(go testはエラーは発生しない)
- dockerコマンドが長いのでmakeなどで省略したい

#参考記事
こちらの記事をかなり参考にさせてもらいました。
https://qiita.com/keitakn/items/f46347f871083356149b