Docker-composeでNext.js + Go (+ nginx) のWebアプリを動かしてみた

# この記事は
- ローカル環境で開発したNext.jsとGoによるWebアプリをDockerコンテナ上で動作するように移植する方法
- Docker-composeを用いた際のnginxのリバースプロキシの方法

について述べる

# あらすじ
以前，個人開発で作成したNext.jsとGoで書いたバックエンドの組み合わせで動作するWebアプリ（ファイルアップローダー）を，実運用するにあたって，Dockerコンテナに移植して，Docker-composeで一発動作するようにしようと思い立った．なので，ゴニョゴニョやったことをまとめる．

# 前提
- ファイルアップローダーはGithubにあるので，それをクローンしてきて勝手にセッティングしてほしい
- フロント側から，バックへfetchする動作があるが，それも1つのドメインで統一して処理できるようにしたい．（Cloudflare tunnnelを使いたい）

    - ということで，nginxのリバプロでなんとかする

# やったこと
まずそれぞれについてDockerfileを作成した．

## 作ったDockerFile
### フロント側（Next.js）
imageは，node.jsの公式イメージを使用した．まず，githubからリポジトリをクローンしてきて，next.jsのルートディレクトリに`WORKDIR`を設定する．

その後，環境変数を設定するために，`.env.local`をルートディレクトリ直下にコピーする．そして，`npm install`で依存関係をインストールし，`npm run build`でビルドしておく．ちなみに，.env.localは実行のたびに読み込まれる．
（参考: https://zenn.dev/aktriver/articles/2022-04-nextjs-env )

下記に，Dockerfileの例を示す．EXPOSEはnextで指定されるアクセスポートを設定しておく．

```dockerfile
FROM node:23.6

WORKDIR /node
EXPOSE 3000

RUN git clone https://github.com/syou551/dirShareApp.git 

WORKDIR /node/dirShareApp/front/dir-share

COPY ./.env.local .
RUN npm install\
    && npm run build
```

### バック側（Go）
imageは，Goの公式のものを用いた．フロント側と同様にリポジトリをクローンして，バック側のディレクトリに移動する．その後，依存モジュールを`go mod tidy`でインストールし，その後ビルドする．
ちなみに，go.modがないと怒られてビルドできないので，開発段階で`go mod init`を忘れないようにしよう．（参考: https://qiita.com/TakanoriVega/items/6d7210147c289b45298a )

```dockerfile
FROM golang:1.23

WORKDIR /go/src

EXPOSE 80

RUN git clone https://github.com/syou551/dirShareApp.git

WORKDIR /go/src/dirShareApp/back

RUN go mod tidy\
    && go build main.go handler.go
```

### nginx
imageはnginxの公式のものを使った．Dockerfileは単に設定ファイルをコピーしているだけである．

```dockerfile
FROM nginx:latest

COPY ./nginx.conf /etc/nginx/conf.d/default.conf
```

本題は，設定ファイルである．リバースプロキシのための基本的な設定は同様であるが，今回はdocker-composeで動かすことを念頭において設定をする．

Dockerでは，docker composeをすると，Networkを明示的に記述しない限りは，それぞれは検出可能となり，それぞれのコンテナへサービス名を指定することでアクセスできるようになる．

- Ex.) serviceがfrontなら，(プロトコル)://front:(ポート)のような形でアクセス可能

(参考: https://docs.docker.jp/compose/networking.html )

これを使って，nginxの`proxy_pass`をサービス名に合わせて設定すれば良い．

また，今回は，外部へ向けて公開するポートは1つに絞りたいため，locationによってフロント側かバック側かを振り分けるようにする．以下の例では，`/dir/`以下のパスへのリクエストは，バックへ流すように設定している．ただし，バック側では`/dir/`を含むリクエストは設定しておらず，リバプロのために勝手につけたパスであるため，取り除いてそれ以降のパスを流すようにしたい．

このためには，`proxy_pass`の最後に`/`をつけることで，`/dir/hoge/huga`へのリクエストは，`{proxy_passで設定した宛先}/hoge/huga`へ飛ばすことができる．ちなみに，`/`をつけない場合は，トリミングされずそのまま渡される．これいつもどっちかわからなくなるので困る．
（参考: https://qiita.com/mkuwan/items/dce68409aaf16142a438 )

```nginx
server{
    listen 80;
    server_name app;

    location /dir/ {
        proxy_pass http://back/;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }

    location / {
        proxy_pass http://front:3000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}
```

## 作ったdocker-compose.yaml
次に，こいつらを`docker compose up`で一括処理できるように，yamlを書いていく．ちなみに，　一応説明すると，docker-compose.yamlは，複数のコンテナをこんなふうに動かしてくださいと指示するための指示書である．

まず，今回は各コンテナに対してすでにDockerfileを作成したので，`build`でDockerfileを配置しているディレクトリ(context)とそのファイル名を指定する．

今回のディレクトリ構成は以下のような形である．
```
FileUploader
|- docker-compose.yaml
|- front
|   |- Dockerfile
|   |- .env.local
|
|- back
|   |- Dockerfile
|
|- nginx
    |- Dockerfile
    |- nginx.conf
```
なので，`context`と`dockerfile`は以下のように設定すれば良い．
```yaml
サービス名:
    build:
      context: ./front
      dockerfile: Dockerfile
```
その後，フロント側とバック側に関しては，起動するために，ルートディレクトリに`working_dir`を設定する．そして，`command`で起動コマンドを入力すれば良い．なお，bashなどを使いたい時は，`command: bash -c "./main"`のように指定する．
(参考: https://qiita.com/ntrlmt/items/cce919c14e4dff07e7ee )

また，nginxに関しては，フロントとバックが起動しているかどうかを確認してから起動するように，`depends_on`を指定しておく．

また，外部に開くポートに関しては，nginxだけを見せれば良いでの，ホスト側の任意のポートとnginxがListenしているポートをマッピングさせておく．
```yaml
    nginx:
        ports:
          - "8080:80"
```

以上の項目を書き込んだyamlの例を以下に示す．

```yaml
services:
  front:
    build:
      context: ./front
      dockerfile: Dockerfile
    working_dir: /node/dirShareApp/front/dir-share
    command: npm run start
  back:
    build:
      context: ./back
      dockerfile: Dockerfile
    working_dir: /go/src/dirShareApp/back
    command: bash -c "./main"
  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    depends_on:
      - front
      - back
    ports:
      - "8080:80"
    
```

# 動作確認
`docker compose up`をすると，自動でコンテナがビルドされ，アプリも起動．今回の設定では，`localhost:8080`にアクセスすれば，next.jsのアプリ画面が開き，操作すると無事バックにもリクエストが通っているのが確認できました．ちなみに，設定ファイルは[github](https://github.com/syou551/homeServer/tree/main/FIleUploader)で公開しています．

# まとめ
Docker composeを使えば，１コマンドで一連のアプリを自動で起動させることができるようになりました！

# その他参考サイト
- https://qiita.com/Nats72/items/d97aee32745e7cf193ef
- https://qiita.com/piny940/items/602316d913938db01c81