Search code examples
nginxdocker-swarmpumaruby-on-rails-7ubuntu-22.04

Rails 7 Action Cable with Puma, Nginx, Docker Swarm


I setup a DigitalOcean Ubuntu 22 droplet to host a docker swarm with containers for a rails app, postgress, and redis. I’m not able to get action cable to work with puma and nginx. I see errors in the browser console for:

WebSocket connection to wss://mydomain.com/cable failed

I followed these guides: Setting up nginx as a reverse proxy: https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-as-a-reverse-proxy-on-ubuntu-22-04

Encrypting nginx: https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04

My /etc/nginx/sites-available/mydomain.com

server {
    server_name mydomain.com www.mydomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        include proxy_params;
    }
    
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


}
server {
    if ($host = www.mydomain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = mydomain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    listen [::]:80;
    server_name mydomain.com www.mydomain.com;
    return 404; # managed by Certbot

}

My cable.yml

development:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://redis:6379" } %>
  channel_prefix: study_notes_development

test:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://redis:6379" } %>
  channel_prefix: study_notes_test

production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://redis:6379" } %>
  channel_prefix: study_notes

my docker-stack.yml

version: '3'

services:
  db:
    image: postgres
    volumes:
      - db_data:/var/lib/postgresql/data
    env_file:
      - .env.production.local
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
  redis:
    image: redis:latest
    volumes:
      - redis:/data
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
  cron:
    image: myimage:cron
    env_file:
      - .env.production
      - .env.production.local
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
  web:
    image: myimage:prod
    env_file:
      - .env.production
      - .env.production.local
    ports:
      - "3000:3000"
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
  db_migrator:
    image: myimage:prod
    command: ["./wait-for","--timeout=300","db:5432","--","bin/rails","db:migrate","db:seed"]
    env_file:
      - .env.production
      - .env.production.local
    deploy:
      restart_policy:
        condition: none

volumes:
  db_data:
  redis:

I've tried variations on nginx settings I found in other questions, such as the accepted answer for this one: What do I need to do to hook up ActionCable on nginx and puma?


Solution

  • I updated my /etc/nginx/sites-available/mydomain.com This is in the first server section under server_name

    location / {
        proxy_pass http://127.0.0.1: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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    
    location /cable {
        proxy_pass http://127.0.0.1:3000/cable;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_redirect off;
    }
    

    I also modified my config/production.rb The HOST variable is the domain name of my server.

      config.action_cable.url = "wss://#{ENV['HOST']}/cable"
      config.action_cable.allowed_request_origins = ["https://#{ENV['HOST']}"]