Recommended docker compose + nginx setup /w websocket support

Well, I did end up solving all my issues.

For once, I am still using two nginx servers. I’m merely using the embedded nginx server to handle the railsserver configuration, as I deemed that too complex to keep external with a docker-compose setup.

Second, I did reference the embedded nginx config to forward the two websockets in my external nginx config the relevant servers (directly to the legacy websockets server, but through the embedded nginx for the railsserver, though I think I could skip the embedded nginx with this one, too).

Finally, I had odd troubles with "CSRF token verification failed!” on login, varied in severity and when it would start occurring on my various trials, but I was able to solve it by adding another header to assure the rails valid_authenticity_token that the connection is proxied with SSL:
proxy_set_header X-Forwarded-Ssl on;
I believe this may be a new requirement in rails and should probably be added to the documentation. In fact, that header IS set in the apache2 example, but NOT for nginx.

This is my final config of the external proxy NGINX with letsencrypt (note that I am configuring SSL options and logging externally, since this is part of a larger setup):

upstream zammad-nginx {
  server localhost:8080;
}

upstream zammad-railsserver {
  server localhost:3000;
}

upstream zammad-websocket {
  server localhost:6042;
}

server {
    listen 80;
    server_name -----------;
    server_tokens off;

    location / {
        return 301 https://$host$request_uri;
    }

    location /.well-known/acme-challenge/ {
        root /var/www/-----------;/html;
    }
}

server {
    listen 443 ssl;
    server_name -----------;
    server_tokens off;

    ssl_certificate     /etc/letsencrypt/live/-----------/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/-----------/privkey.pem;

    add_header Strict-Transport-Security "max-age=63072000" always;

    client_max_body_size 50M;

    # legacy web socket server
    location /ws {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $http_host;
        proxy_set_header CLIENT_IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_read_timeout 86400;
        proxy_pass http://zammad-websocket;
    }

    # action cable
    location /cable {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $http_host;
        proxy_set_header CLIENT_IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_read_timeout 86400;
        proxy_pass http://zammad-nginx;
    }

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header CLIENT_IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_read_timeout 180;
        proxy_pass http://zammad-nginx;

        gzip on;
        gzip_types text/plain text/xml text/css image/svg+xml application/javascript application/x-javascript application/json application/xml;
        gzip_proxied any;
    }
}

For this to work, I also slightly modified the docker-compose.yml:

  zammad-railsserver:
    ports:
      - "3000:3000"
  zammad-websocket:
    ports:
      - "6042:6042"

Notably, the nginx container is staying the same. You should probably put this in the docker-compose.override.yml as recommended.
I think that should be it. If I’m missing something, feel free to ask. I’ll be wanting to keep my setup up-to-date anyway.