reactjsruby-on-railsdockernginx

Deploy Backend and Frontend projects with NGINX


I'm setting up a web application using Nginx and Docker with a React frontend and a Rails backend API, all on a single domain. I want to serve the frontend directly from the root (example.com) and route API requests to the Rails backend when they start with /api.

Here's the setup I'm using:

Requests to example.com should serve the React frontend. Requests to example.com/api/* should be routed to the Rails API backend.

Here’s the structure:

/root/app/ 
├── docker-compose.yml         
├── nginx.conf                 
├── backend/                   # Rails API 
│   ├── Dockerfile             # Dockerfile
│   ├── config/routes.rb       # Routes
└── frontend/                  # React Web App
    ├── Dockerfile             # Dockerfile

docker-compose.yml File

version: '3'

services:
  # Rails Backend 
  rails_app:
    build: ./backend
    volumes:
      - ./backend:/app
    ports:
      - "4000:4000"
    environment:
      RAILS_ENV: production
      DATABASE_URL: postgres://your_username:your_password@db:5432/your_database_name
      SECRET_KEY_BASE: xxxxxxxxxxxxxxxxxxxx
    depends_on:
      - db

  # React Frontend
  react_app:
    build: ./frontend
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - '3000:3000'  #
    environment:
      - NODE_ENV=production
  # PostgreSQL 
  db:
    image: postgres:latest
    ports:
      - '5433:5432'
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: your_username
      POSTGRES_PASSWORD: your_password
      POSTGRES_DB: your_database_name

  # Nginx
  nginx:
    image: nginx:latest
    ports:
      - '80:80'
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - rails_app
      - react_app

volumes:
  pgdata:

nginx.conf file

events {}

http {
  
  server {
    listen 80;
    server_name example.com www.example.com;

# Rails App
    location /api/ {
      proxy_pass http://rails_app:4000;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }
    
# React App
    location / {
      proxy_pass http://react_app:3000;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }
  }
}

backend/config/routes.rb File

Rails.application.routes.draw do
  scope "/api" do
    resources :posts  # /api/posts
    get "up", to: "rails/health#show", as: :rails_health_check  # /api/up 
  end
end

The Issue

When I visit example.com, the React frontend loads as expected. However, when I try to access example.com/api, I don't receive any response from the Rails API backend. Nginx logs show repeated 301 Moved Permanently responses, leading to a redirect loop.

www.example.com/

React frontend response is ok

www.example.com/api/posts

ginx-1 | [07/Nov/2024:23:27:21 +0000] "GET /api/posts HTTP/1.1" 301 0 ginx-1 | [07/Nov/2024:23:27:21 +0000] "GET /api/posts HTTP/1.1" 301 0 ginx-1 | [07/Nov/2024:23:27:21 +0000] "GET /api/posts HTTP/1.1" 301 0 ginx-1 | [07/Nov/2024:23:27:21 +0000] "GET /api/posts HTTP/1.1" 301 0

Question

How can I properly configure Nginx and Docker to route API requests under /api to my Rails backend without causing a redirect loop? Any advice would be greatly appreciated!


Solution

  • Answer:

    If config.force_ssl = true is set in your Rails application, it forces all incoming HTTP requests to redirect to HTTPS. When Nginx is configured to pass requests to Rails over HTTP, this setting creates a redirect loop. Rails redirects HTTP requests to HTTPS, but Nginx forwards the request back to Rails over HTTP, causing an infinite loop.

    Solution:

    To resolve the loop and allow your Rails API to function correctly, open config/environments/production.rb and disable the force_ssl setting:

    config.force_ssl = false
    

    This change stops Rails from redirecting to HTTPS and allows it to accept requests over HTTP. As a result, Nginx will forward /api/ requests to Rails without triggering a redirect loop.