Rails + Nginxの本番環境をDockerで構築した話

## はじめに

以前Railsの本番環境をunicornで立てる実装をしました。

https://qiita.com/piny940/items/23a3bac7f1d867b989dd

ただ、サーバーをVPSから自宅サーバーに移行する際、サーバーをすべてdockerで立てようとした時にnginx周りとかで色々と詰まったことがあったので記事に記録を残しました。

## ディレクトリ構成
```
- root
  - app
  - bin
  - ...(rails関連のファイル)
  - public
  - .env
  - Dockerfile
  - docker-compose.yml
  - nginx
    - Dockerfile
    - nginx.conf
```

## Docker関連
```Dockerfile.
FROM ruby:3.1.2-alpine as builder

RUN apk update && \
  apk add --no-cache \
  build-base \
  postgresql-dev

RUN gem install bundler
COPY Gemfile Gemfile.lock ./
ENV RAILS_ENV=production
RUN RAILS_ENV=${RAILS_ENV} bundle install

FROM ruby:3.1.2-alpine as app

RUN apk update && \
  apk add --no-cache \
  tzdata \
  postgresql-client

WORKDIR /app

COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY . .

CMD ["sh", "entrypoint.sh"]
```

マルチステージビルド(https://docs.docker.jp/develop/develop-images/multistage-build.html )を使うことで作られるdocker imageのサイズを小さくする工夫をしてあります。

参考: https://qiita.com/sasamuku/items/6aea2b4dffb43de161e3

```docker-compose.yml
services:
  rails:
    build:
      context: .
      dockerfile: Dockerfile
    image: rails-app
    container_name: rails-app-rails
    depends_on:
      - db
    env_file:
      - .env
    environment:
      - RAILS_ENV=production
    volumes:
      - public:/app/public
  db:
    image: postgres:15-alpine
    restart: always
    container_name: rails-app-postgres
    volumes:
      - database:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: rails_app_production
  
  nginx:
    container_name: rails-app-nginx
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ports:
      - 54010:80
    depends_on:
      - rails
    volumes:
      - public:/app/public

volumes:
  database:
  public:
```
- Railsのpublicディレクトリをvolumeとしてnginxと共有し、nginx上で公開できるようにしています。公開するポートについては、dockerのコンテナ内ネットワークを使ってrailsサーバーのポートへnginxがプロキシするようにしています。
- `env_file`に`.env`を指定することで、`.env`の内容がコンテナ内の環境変数に入るようにしています。
- nginxについては`nginx/Dockerfile`を元にビルドをするようにしています


```nginx/Dockerfile.
FROM nginx:alpine

RUN rm -f /etc/nginx/conf.d/*

ADD nginx.conf /etc/nginx/conf.d/nginx.conf

CMD /usr/sbin/nginx -g 'daemon off;'
```
nginxのDockerfileです。設定ファイル(.conf)をコピーしてnginxサーバーを起動しています。

## Rails関係
```.env
POSTGRES_USER=...
POSTGRES_PASSWORD=...
```
`.env`には`POSTGRES_USER`と`POSTGRES_PASSWORD`を指定します。

```config/database.yml
production:
  <<: *default
  database: rails_app_production
  username: <%= ENV["POSTGRES_USER"] %>
  password: <%= ENV["POSTGRES_PASSWORD"] %>
  host: db
```
Railsのデータベースの設定を記述します。hostはdockerコンテナ内ネットワークを使用してpostgresサーバーと接続できるようにします。

```entrypoint.sh
bin/rails db:migrate
bin/rails assets:precompile
bin/rails server -b 0.0.0.0 -p 3000
```
サーバー起動時に実行されるコマンドを設定します。起動時に`db:migrate`を実行することで起動時にデータベースのスキーマを反映させることができます。`assets:precompile`は多分`Dockerfile`の中で行なっても大丈夫だと思う(要検証)。

## Nginx関係
```nginx/nginx.conf
server {
  listen 80;
  server_name www.example.com;
	root /app/public;

  location @app {
    proxy_pass http://rails:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
	try_files $uri/index.html $uri @app;
	error_page 500 502 503 504 /500.html;
}
```
Railsのpublicディレクトリを直接公開するよう設定をし、それ以外のURLに対してはRailsサーバーにリクエストを送るようにしています。

## おわりに
今回はRails + Nginxサーバーをdockerを用いて公開するよう設定をしました。次回はRailsサーバーとフロントエンドのNextサーバーを組み合わせてdockerで本番環境を構築していこうと思います。

## 参考資料

https://docs.docker.jp/develop/develop-images/multistage-build.html

https://qiita.com/sasamuku/items/6aea2b4dffb43de161e3

https://docs.docker.jp/compose/environment-variables.html