Nginx+Next.js+Rails+PostgreSQLを使用した開発環境構築

概要

再度環境構築の話となりますが、タイトルにある通りNginxのリバースプロキシを使用しNext.jsRuby on RailsDockerを使用した開発環境の構築についての手順を紹介します。

前回同様データベースはPostgreSQLを使用しようと思います。

構成

2022年6月22日時点での環境を示します。

  • macOS Monterey 12.4
  • Docker 20.10.14
  • Docker Compose v2.5.1
  • Nginx 1.21.6
  • Node.js v18.4.0
  • Next.js 12.1.6
  • Ruby 3.1.2
  • Ruby on Rail 7.0.3
  • PostgreSQL 14.3

ディレクトリ構造

プロジェクトルート
├── docker-compose.yml
├── container
│   ├── backend
│   │   └── Dockerfile
│   │   └── entrypoint.sh
│   ├── db
│   ├── frontend
│   │   └── Dockerfile
│   └── nginx
│       └── default.conf
│       └── nginx.conf
├── backend
│   ├── Gemfile
│   └── Gemfile.lock
└── frontend

プロジェクトの作成

Next.js(フロントエンド側)のプロジェクト作成

  • docker-compose.yml
version: "3"

services:
  frontend:
    container_name: frontend
    build:
      context: .
      dockerfile: container/frontend/Dockerfile
    volumes:
      - ./frontend:/usr/src/app
    command: yarn dev
    ports:
      - "3000:3000"
  • container/frontend/Dockerfile
FROM node:18-alpine3.15

# 環境変数の設定
ENV APP_PATH /usr/src/app

WORKDIR $APP_PATH

Next.jsのプロジェクトを作成する

docker compose run --rm frontend npx create-next-app . --ts

Ruby on Rails(バックエンド側)のプロジェクト作成

  • docker-compose.yml(修正)
version: "3"

services:
  frontend:
    container_name: frontend
    build:
      context: .
      dockerfile: container/frontend/Dockerfile
    volumes:
      - ./frontend:/usr/src/app
    command: yarn dev
    ports:
      - "3000:3000"
+    depends_on:
+      - backend

+  backend:
+    container_name: backend
+    build:
+      context: .
+      dockerfile: container/backend/Dockerfile
+    volumes:
+      - ./backend:/app
+    environment:
+      TZ: Asia/Tokyo
+      RAILS_ENV: development
+    ports:
+      - "3001:3000"
+    depends_on:
+      - db

+  db:
+    image: postgres:14.3-alpine
+    container_name: postgres
+    environment:
+      - TZ=Asia/Tokyo
+      - PGTZ=Asia/Tokyo
+      - POSTGRES_PASSWORD=password
+    volumes:
+      - ./container/db/data:/var/lib/postgresql/data
+    ports:
+      - "5432:5432"
  • container/backend/Dockerfile
FROM ruby:3.1.2-alpine

ENV ROOT="/app"
ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo

WORKDIR ${ROOT}

COPY ./backend/Gemfile ./backend/Gemfile.lock ${ROOT}

RUN apk update && \
    apk add \
    alpine-sdk \
    build-base \
    sqlite-dev \
    postgresql-dev \
    tzdata \
    git \
    gcompat

RUN gem install bundler
RUN bundle install

COPY ./container/backend/entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

CMD ["rails", "server", "-e"]
  • container/backend/entrypoint.sh
#!/bin/sh
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
  • backend/Gemfile
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.1.2"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0.3"
  • backend/Gemfile.lock
空ファイル

Ruby on Railsのプロジェクトを作成する

docker compose run --rm backend bundle exec rails _7.0.3_ new . -d postgresql -f

コンテナをビルドする

docker compose build

データベース情報を設定する

 default: &default
   adapter: postgresql
   encoding: unicode
   # For details on connection pooling, see Rails configuration guide
   # https://guides.rubyonrails.org/configuring.html#database-pooling
   pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+  username: postgres
+  password: password
+  host: db

データベースを作成する

docker compose run --rm backend rails db:create

コンテナを立ち上げる

docker compose up -d

http://localhost:3000/へアクセスし、フロントエンド側の画面が表示されることを確認する

http://localhost:3001/へアクセスし、バックエンド側の画面が表示されることを確認する

コンテナを落とす

docker compose down

Nginxの設定を追加

  • docker-compose.yml(再修正)
version: "3"

services:
+  web:
+    image: nginx:1.21.6
+    container_name: web
+    environment:
+      TZ: Asia/Tokyo
+    volumes:
+      - ./container/nginx/default.conf:/etc/nginx/conf.d/default.conf
+      - ./container/nginx/log:/var/log/nginx
+      - ./backend/public:/backend/public
+      - ./backend/tmp:/backend/tmp
+    ports:
+      - "80:80"
+    depends_on:
+      - frontend
+      - backend

  frontend:
    container_name: frontend
    build:
      context: .
      dockerfile: container/frontend/Dockerfile
    volumes:
      - ./frontend:/usr/src/app
    command: yarn dev
    ports:
      - "3000:3000"
    depends_on:
      - backend

  backend:
    container_name: backend
    build:
      context: .
      dockerfile: container/backend/Dockerfile
    volumes:
      - ./backend:/app
    environment:
      TZ: Asia/Tokyo
      RAILS_ENV: development
    ports:
      - "3001:3000"
    depends_on:
      - db

  db:
    image: postgres:14.3-alpine
    container_name: postgres
    environment:
      - TZ=Asia/Tokyo
      - PGTZ=Asia/Tokyo
      - POSTGRES_PASSWORD=password
    volumes:
      - ./container/db/data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
  • container/nginx/default.conf
upstream backend {
  server unix:///backend/tmp/sockets/puma.sock fail_timeout=30s;
}

server {
  listen 80;
  # ドメインもしくはIPを指定
  server_name localhost;

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  ########################################
  # リバースプロキシ関連の設定
  ########################################
  # 公開側(フロントエンド)
  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://frontend:3000/;
  }

  # 管理側(バックエンド)
  location /manage/ {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://backend;
  }
}
  • container/nginx/nginx.conf
user  root;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;
    include /etc/nginx/conf.d/*.conf;
}

バックエンド側のPumaの設定を変更する

  • backend/config/puma.rb
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
#
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
- port ENV.fetch("PORT") { 3000 }
+ #port ENV.fetch("PORT") { 3000 }
+ bind "unix://#{Rails.root}/tmp/sockets/puma.sock"

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }

# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart

バックエンド側のルート設定を変更する

  • backend/config/environments/development.rb
require "active_support/core_ext/integer/time"

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded any time
  # it changes. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports.
  config.consider_all_requests_local = true

  # Enable server timing
  config.server_timing = true

  # Enable/disable caching. By default caching is disabled.
  # Run rails dev:cache to toggle caching.
  if Rails.root.join("tmp/caching-dev.txt").exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true

    config.cache_store = :memory_store
    config.public_file_server.headers = {
      "Cache-Control" => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false

    config.cache_store = :null_store
  end

  # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :local

  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false

  config.action_mailer.perform_caching = false

  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log

  # Raise exceptions for disallowed deprecations.
  config.active_support.disallowed_deprecation = :raise

  # Tell Active Support which deprecation messages to disallow.
  config.active_support.disallowed_deprecation_warnings = []

  # Raise an error on page load if there are pending migrations.
  config.active_record.migration_error = :page_load

  # Highlight code that triggered database queries in logs.
  config.active_record.verbose_query_logs = true

  # Suppress logger output for asset requests.
  config.assets.quiet = true

  # Raises error for missing translations.
  # config.i18n.raise_on_missing_translations = true

  # Annotate rendered view with file names.
  # config.action_view.annotate_rendered_view_with_filenames = true

  # Uncomment if you wish to allow Action Cable access from any origin.
  # config.action_cable.disable_request_forgery_protection = true

+  Rails.application.config.relative_url_root = "/manage"
end
  • backend/config.ru
# This file is used by Rack-based servers to start the application.

require_relative "config/environment"

- run Rails.application
+ map ActionController::Base.config.relative_url_root || "/" do
+   run Rails.application
+ end
Rails.application.load_server

コンテナを再度立ち上げる

docker compose up -d

http://localhost/へアクセスしフロントエンド側が表示されることを確認する

http://localhost/manage/へアクセスしバックエンド側が表示されることを確認する

終わりに

弊社にお仕事を依頼したいお客様がいらっしゃいましたら、以下のフォームもしくはメールにてお気軽にお問い合わせ下さい。

お問合せ

システム開発部 森岡(morioka_tatsuaki@computer-tb.co.jp

  • X