Acho que um sistema docker com nginx + ssl + nodered + grafana + mosquitto + influxdb é bastante comum. Espero que isso ajude outros.
Tenho o nginx e o ssl (certbot) funcionando perfeitamente, e se eu for para http://example.com:1880 ou http://example.com:1880/ui ou http://example.com:3000 consigo acessar perfeitamente os fluxos nodered, ui e grafana.
Mas eu preciso de "mais".
Quero ter "subpastas" como esta:
- grafana irá para example.com/grafana
- A edição do fluxo nodered deve ir para example.com/nodered
- a interface do usuário nodered (painel ou flowfuse, tanto faz) deve ir para example.com
Tentei centenas de configurações editando docker-compose.yml, nginx.conf, settings.js e grafana.ini, mas... sem sorte.
Espero que alguém possa ter algo semelhante e compartilhar conosco seus arquivos de configuração.
Estas são as minhas neste momento (eu apaguei minhas "tentativas de configuração erradas")
server {
listen 80;
listen [::]:80;
server_name example.com;
location /.well-known/acme-challenge {
allow all;
root /var/www/certbot;
}
location / {
# rewrite ^ https://$host$request_uri? permanent;
return 301 https://$server_name$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
#STATIC index index.php index.html index.htm;
#STATIC root /var/www/html;
server_tokens off; #Disable the Nginx version in headers for security
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
#linuxserver.io
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Logs for Nginx access and errors
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location ~ /\.ht { deny all; }
location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { log_not_found off; access_log off; allow all; }
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { expires max; log_not_found off; }
}
Docker-compose.yml
services:
mosquitto:
image: eclipse-mosquitto:2
container_name: mosquitto
ports:
- "1883:1883"
#- "8883:8883"
#- "9001:9001" # Websocket opcional
volumes:
- ./mosquitto-data:/mosquitto
restart: unless-stopped
#environment:
# - 'TZ='Europe/Brussels'
influxdb:
image: influxdb:2.7
container_name: influxdb
ports:
- "8086:8086"
volumes:
- ./influxdb-data:/var/lib/influxdb2
- ./influxdb-config:/etc/influxdb2
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=user
- DOCKER_INFLUXDB_INIT_PASSWORD=password
- DOCKER_INFLUXDB_INIT_ORG=org
- DOCKER_INFLUXDB_INIT_BUCKET=buck
- DOCKER_INFLUXDB_INIT_RETENTION=0
restart: unless-stopped
grafana:
image: grafana/grafana:11.4.0
container_name: grafana
ports:
- "3000:3000"
volumes:
- ./grafana-data:/var/lib/grafana
- ./grafana-config:/etc/grafana
#user: "0"
environment:
- GF_SECURITY_ADMIN_USER=user
- GF_SECURITY_ADMIN_PASSWORD=password
#- GF_SERVER_ROOT_URL=https://example.com/grafana
#- GF_SERVER_DOMAIN=https://example.com/
#- GF_INSTALL_PLUGINS
restart: unless-stopped
nodered:
image: nodered/node-red:4.0
container_name: nodered
ports:
- "1880:1880"
volumes:
- ./nodered-data:/data
restart: unless-stopped
nginx:
image: nginx:1.27.3-bookworm
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx-data:/etc/nginx/conf.d
- ./nginx-logs:/var/log/nginx
- ./letsencrypt-data:/etc/letsencrypt
- ./certbot-data:/var/www/certbot
depends_on:
- nodered
- grafana
- influxdb
restart: unless-stopped
certbot:
image: certbot/certbot
container_name: certbot
depends_on:
- nginx
volumes:
- ./letsencrypt-data:/etc/letsencrypt
- ./certbot-data:/var/www/certbot
#restart: unless-stopped
# command: certonly --webroot --webroot-path=/var/www/certbot/ --email [email protected] --agree-tos --no-eff-email --staging -d example.com
#command: certonly --webroot --webroot-path=/var/www/certbot/ --email [email protected] --agree-tos --no-eff-email --force-renewal -d example.com
volumes:
mosquitto-data:
influxdb-data:
influxdb-config:
grafana-data:
grafana-config:
nodered-data:
letsencrypt-data:
nginx-data:
certbot-data:
O que preciso alterar/adicionar a esses arquivos?:
- nodered: docker-compose, nginx e settings.js
- grafana: docker-compose, nginx e grafana.ini
Muito obrigado.
2025/01/15 19:00 (UTC): Comecei com o grafana para ir passo a passo (acho que é mais fácil) Mudei o docker-compose para:
grafana:
image: grafana/grafana:11.4.0
container_name: grafana
ports:
- "3000:3000"
volumes:
- ./grafana-data:/var/lib/grafana
- ./grafana-config:/etc/grafana
#user: "0"
environment:
- GF_SECURITY_ADMIN_USER=user
- GF_SECURITY_ADMIN_PASSWORD=password
- GF_SERVER_ROOT_URL=https://example.com/grafana/
- GF_SERVER_DOMAIN=https://example.com/
- GF_SERVER_SERVER_FORM_SUB_PATH=true
restart: unless-stopped
E nginx.conf:
# Grafana Dashboard
location /grafana/ {
proxy_set_header Host $host;
proxy_pass http://grafana:3000/;
}
# Proxy Grafana Live WebSocket connections.
location /grafana/api/live/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_pass http://grafana:3000/;
}
Não alterei o grafana.ini porque encontrei uma maneira melhor de usar variáveis de ambiente no docker-compose.
Estou entendendo "alguma coisa", pelo menos, "grafana" está respondendo:
If you're seeing this Grafana has failed to load its application files
This could be caused by your reverse proxy settings.
If you host grafana under a subpath make sure your grafana.ini root_url setting includes subpath. If not using a reverse proxy make sure to set serve_from_sub_path to true.
If you have a local dev build make sure you build frontend using: yarn start, or yarn build.
Sometimes restarting grafana-server can help.
Check if you are using a non-supported browser. For more information, refer to the list of supported browsers .
Então ainda não consigo fazê-lo funcionar.
2025/01/15 20:30 (UTC): Aqui estou testando o nodered, a configuração de "proxy" mais simples.
Adicionei isso em nginx.conf
location / {
proxy_pass http://nodered:1880/;
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;
# # WebSocket support
proxy_http_version 1.1;
# proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
rewrite ^/(.*) /$1 break;
}
Eu tentei várias tentativas comentando e descomentando cada linha (cada tentativa === uma linha comentada ou descomentada)
Novamente resultado parcial. Posso ver que o nodered está respondendo, mas com uma página em branco, pois várias páginas estáticas não estão carregando.
Estes são alguns dos erros que posso ver no console:
Failed to load resource: the server responded with a status of 404 ()Understand this errorAI
example.com/:1 Refused to execute script from 'https://example.com/vendor/vendor.js?v=3305aad6c0c6' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.Understand this errorAI
monaco-bootstrap.js:1
Failed to load resource: the server responded with a status of 404 ()Understand this errorAI
red.min.js:1
Failed to load resource: the server responded with a status of 404 ()Understand this errorAI
main.min.js:1
Então, novamente, estou me sentindo estúpido sobre isso. Provavelmente isso também é fácil de resolver, eu tentei todas as coisas que encontrei pesquisando no Google e usando chatgpt (por exemplo)
Mais uma vez obrigado pela sua ajuda.
2025/01/18 9:00 (UTC):
Continuo tentando coisas diferentes. Desta vez a configuração mais simples do grafana apenas para testar as configurações docker-compose e nginx. Sem sorte de novo.
# Grafana Dashboard
location / {
proxy_set_header Host $host;
proxy_pass http://grafana:3000/;
}
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
volumes:
- ./grafana-data:/var/lib/grafana
- ./grafana-config:/etc/grafana
#user: "0"
environment:
- GF_SECURITY_ADMIN_USER=user
- GF_SECURITY_ADMIN_PASSWORD=password
E grafana.ini NÃO foi modificado do arquivo padrão.
Consigo ver a página web do Grafana com erro e, no console, na aba de rede, consigo ver que o Chrome não carrega arquivos CSS e JS.
https://example.com/public/build/grafana.dark.722d809dba5a31f57d49.css
Verifiquei que dentro do contêiner grafana esse arquivo existe, então não sei por que o nginx não está redirecionando ou encontrando esse arquivo.
Mas, este arquivo foi encontrado: https://example.com/public/img/grafana_icon.svg
Então há algum problema no arquivo de configuração do nginx com arquivos js, css, ..., não é?
Se eu for (testar) em http://example.com:3000 os arquivos são carregados corretamente: http://example.com:3000/public/build/grafana.dark.722d809dba5a31f57d49.css
Então...alguma solução?
(só por precaução, o arquivo nginx.conf que estou usando para este teste "simples":)
server {
listen 80;
listen [::]:80;
server_name example.com;
location /.well-known/acme-challenge {
allow all;
root /var/www/certbot;
}
location / {
rewrite ^ https://$host$request_uri? permanent;
# return 301 https://$server_name$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
server_tokens off; #Disable the Nginx version in headers for security
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Grafana Dashboard
location / {
proxy_set_header Host $host;
proxy_pass http://grafana:3000/;
}
# Logs for Nginx access and errors
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location ~ /\.ht { deny all; }
location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { log_not_found off; access_log off; allow all; }
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { expires max; log_not_found off; }
}
O que você está tentando alcançar é comumente chamado de "hospedar um aplicativo da web sob um prefixo URI" ou "hospedar um aplicativo da web sob um caminho base diferente do root" . Embora essa tarefa possa parecer direta, na prática, ela costuma ser mais complexa do que parece (e, em alguns casos, até mesmo impossível sem modificar o código-fonte do aplicativo da web de backend). Para não me repetir, recomendo consultar esta resposta para uma explicação abrangente do porquê disso.
Felizmente, tanto o Node-RED quanto o Grafana oferecem suporte à configuração para hospedagem sob um prefixo URI. Para fluxos do Node-RED, consulte os parâmetros de configuração , e descritos
httpAdminRoot
aquihttpNodeRoot
. Para a IU do Node-RED, consulte o capítulo "Configurações" na página da Web do Node-RED Dashboard :httpRoot
No entanto, uma limitação potencial a ter em mente é que pode ser impossível ter o endereço da web da UI como algo diferente de um subcaminho do endereço da web dos fluxos do Node-RED. Isso deve ser verificado.
Para configurar corretamente o Grafana, consulte o capítulo "Alternativa para servir o Grafana sob um subcaminho" do tutorial "Executar o Grafana atrás de um proxy reverso". O
[server]
capítulo do arquivo de configuração do Grafana deve conter algo comoDepois que seus aplicativos de backend estiverem configurados corretamente, você não precisará de nenhuma manipulação de URLs de solicitação por meio da configuração nginx. Supondo que você consiga configurar os fluxos do Node-Red sob o
/nodered
prefixo URI enquanto mantém a IU do Node-Red sob a raiz do servidor web, sua configuração nginx final ficará assim:Você não precisa expor portas abertas para o mundo externo para nenhum contêiner, exceto o
nginx
one. Considere alterar as declarações comopara
para todos os outros contêineres (veja o tópico "Qual é a diferença entre ports e exhibit no docker-compose?" do Stack Overflow).
muito obrigado Ivan Shatsky pela sua ajuda. Para mim isso tem sido um pesadelo, fazer centenas de testes. Finalmente, tentei comentar portas e .... voilá!!! O que eu tinha para testar contêineres ( http://example.com:3000 por exemplo) estava produzindo algo que o nginx ou o próprio contêiner não gosta.
Finally here you have the answer, this is docker-compose.yml and nginx.confg files to have working grafana (/graf), nodered admin (/nr) and nodered ui (/) and influxdb (/db, this is very complex). All of them load perfectly, and I hope (next weeks) they will work without any problem.
I hope someone wuth the same problem/objective could find this answer good.