You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails 5.2+Docker+Nginx环境下Passenger转Puma后静态资源加载失败

问题描述

我在Rails 5.2项目中尝试从Passenger迁移至Puma,项目采用Docker部署,通过Nginx实现HTTP转HTTPS并将请求转发至Rails容器。此前用Passenger时架构运行正常,换成Puma后,Nginx与Puma能正常通信,动态内容加载正常,但所有静态资源都无法访问,始终返回301永久重定向。我不想开启Rails静态资源服务(出于性能考虑),且相同配置在Passenger环境下能正常运行。

wget测试静态资源结果

wget http://myserver.de/assets/application-606786f053f1b452d1089f9f5e4741add1d55c6d89223168b844dd9f21740aee.css
URL transformed to HTTPS due to an HSTS policy
--2024-07-08 16:21:27--  https://myserver.de/assets/application-606786f053f1b452d1089f9f5e4741add1d55c6d89223168b844dd9f21740aee.css
Auflösen des Hostnamens myserver.de (myserver.de)… xxx.xxx.xxx.xxx
Verbindungsaufbau zu myserver.de (myserver.de)|xxx.xxx.xxx.xxx|:443 … verbunden.
HTTP-Anforderung gesendet, auf Antwort wird gewartet … 301 Moved Permanently
Platz: https://myserver.de/404.html [folgend]
--2024-07-08 16:21:27--  https://myserver.de/404.html
Wiederverwendung der bestehenden Verbindung zu myserver.de:443.
HTTP-Anforderung gesendet, auf Antwort wird gewartet … 301 Moved Permanently
Platz: https://myserver.de/404.html [folgend]
--2024-07-08 16:21:27--  https://myserver.de/404.html
Wiederverwendung der bestehenden Verbindung zu myserver.de:443.
HTTP-Anforderung gesendet, auf Antwort wird gewartet … 301 Moved Permanently
...

德语内容翻译

  • Auflösen des Hostnamens = 解析主机名
  • Verbindungsaufbau zu = 连接至
  • HTTP-Anforderung gesendet, auf Antwort wird gewartet = 已发送HTTP请求,等待响应
  • Wiederverwendung der bestehenden Verbindung zu = 复用与...的现有连接

补充配置文件

Nginx 配置

# NGINX config:

server {
    listen       80;
    listen  [::]:80;
    server_name  mydomain.de;

    access_log  /var/log/nginx/my.access.log  main;
    error_log  /var/log/nginx/my.error_log  warn;
    
    proxy_connect_timeout  900;
    proxy_send_timeout    900;
    proxy_read_timeout    900;
    send_timeout      900;
    # for issuing certificates
    #
    #location ~ /.well-known/acme-challenge/ {
    #     root /var/www/certbot;
    #}

    location / {
        return 301 https://$host$request_uri;
    #     proxy_pass http://app:3000/;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

server {
    listen 443 ssl;
    http2 on;
    ssl_certificate     /etc/letsencrypt/live/mydomain.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mydomain.de/privkey.pem;
    server_name mydomain.de;
    root /var/www/html;
    index index.html;

    client_max_body_size 20M;
    access_log  /var/log/nginx/my.access.log  main;
    error_log  /var/log/nginx/my.error_log  warn;

    proxy_connect_timeout  900;
    proxy_send_timeout    900;
    proxy_read_timeout    900;
    send_timeout      900;

    # for re-issuing certificates
    #
    location ~ /.well-known/acme-challenge/ {
         root /var/www/certbot;
    }

    location / {
        proxy_pass https://app_net:3001/;
        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-Host $server_name;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

Docker Compose 配置

# DOCKER-COMPOSE.yml
services:
  proxy:
    image: nginx
    container_name: nginx_proxy
    restart: unless-stopped
    networks:
      mynetwork:
        ipv4_address: 192.168.80.10
    ports:
    - "80:80"
    - "443:443"
    volumes:
    - /root/volumes/nginx/conf:/etc/nginx/conf.d:ro
    - /root/volumes/nginx/certbot/conf:/etc/letsencrypt
    - /root/volumes/nginx/certbot/www:/var/www/certbot


  app:
    image: myrepo/myapp
    depends_on:
    - db
    container_name: app
    restart: always
    volumes:
      source: /root/volumes/nginx/certbot/conf
      target: /myhomedir/config/certs
    environment:
    ...

Docker 入口脚本

# DOCKER_ENTRYPOINT.sh
#! /usr/bin/env bash

cron
crontab /etc/cron.d/my_crontab
touch ./log/cron.log

sleep 5 && bundle exec rake db:migrate
bundle exec rake tmp:clear
bundle exec puma -b 'ssl://0.0.0.0:3001?key=/myhomedir/config/certs/live/mydomain.de/privkey.pem&cert=/myhomedir/config/certs/live/mydomain.de/fullchain.pem'

问题分析与解决方案

核心问题

  1. Nginx未配置静态资源直接处理:当前Nginx把所有请求(包括/assets/路径)都转发给了Puma,但Puma默认不会处理静态资源(除非开启Rails静态服务),导致请求找不到资源后触发301重定向到404页面,进而陷入循环重定向。
  2. Passenger与Puma的差异:Passenger默认会自动处理Rails的静态资源请求,而Puma不会,这就是之前Passenger正常、Puma出问题的关键原因。

解决方案

修改Nginx的HTTPS server配置,添加专门处理/assets/路径的规则,直接从Rails编译后的静态资源目录读取文件:

步骤1:确认Rails静态资源目录

在Docker的Rails容器中,静态资源编译后默认存放在/myhomedir/public/assets/(假设你的Rails项目根目录是/myhomedir)。

步骤2:修改Nginx配置

在HTTPS的server块中,添加以下location /assets/规则,放在location /之前:

location /assets/ {
    # 方法1:直接挂载Rails的public目录到Nginx(推荐)
    root /path/to/rails/public; # 需在Docker Compose中给Nginx添加对应volume
    # 方法2:如果静态资源已预编译并复制到Nginx容器的/var/www/html/assets,可直接用:
    # root /var/www/html;
    expires 1y;
    add_header Cache-Control public;
    add_header ETag "";
    break;
}

步骤3:调整Docker Compose配置

给Nginx服务添加volume,挂载Rails容器的public目录(或者预编译后的静态资源目录):

proxy:
  # ... 其他配置不变
  volumes:
    # ... 现有volume
    - /root/volumes/rails/public:/path/to/rails/public:ro # 替换为实际路径

步骤4:验证配置

重启Nginx和Rails容器,再次用wget测试静态资源URL,确认返回200状态码。

额外排查点

  • 检查Rails是否已执行rails assets:precompile,确保public/assets目录下有编译后的静态文件。
  • 确认Nginx容器中挂载的静态资源目录权限正确,Nginx进程能读取文件。
  • 检查Puma启动命令是否正确,确保没有额外的重定向配置。

内容的提问来源于stack exchange,提问作者MDickten

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 12:35:04