allinurl/goaccess

GoAccess using Docker behind a Traefik proxy

Closed this issue · 2 comments

Hello, I'm trying to set up goaccess in Docker behind a traefik proxy. The example in the goaccess repo is helpful, but doesn't get me working and it doesn't explain its changes, e.g. why is it using port 443?

I've attached a screenshot from my browser.

Image

I should also note that I get what looks like a good connection via curl

$ curl -v -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" https://goaccess.MYDOMAIN.TLD
... bunch of protocol stuff ...
accept-ranges: bytes
content-type: text/html
date: Sun, 01 Jun 2025 17:00:18 GMT
etag: "683c8511-b5414"
last-modified: Sun, 01 Jun 2025 16:51:29 GMT
permissions-policy: interest-cohort=()
server: nginx/1.27.5
strict-transport-security: max-age=31536000; includeSubDomains; preload
content-length: 742420

<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'>
...
r.sphericalTriangleArea=function(r){return 2*nr(w(r,!1))},Object.defineProperty(r,"__esModule",{value:!0})});</script></body></html>

However, websocat shows problems:

$ ./websocat.x86_64-unknown-linux-musl -t --log-verbose wss://goaccess.MYDOMAIN.TLD
2025-06-01T17:11:32.871822Z ERROR websocat::scenario_executor::utils1: Upstream server returned status code other than `switching protocols`: 200 OK

I have about six files of configuration. Three for treafik (base/static/dynamic), one for goaccess compose, one nginx, and one goaccess.conf.

I haven't made changes to the nginx.conf compared to the one in the goaccess repo.

goaccess/docker-compose.yml

services:
  goaccess-nginx:
    image: nginx:alpine
    container_name: goaccess-nginx
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    volumes:
      - /srv/goaccess/config/nginx.conf:/etc/nginx/nginx.conf:ro
      - goaccess-data:/usr/share/nginx/html:ro
    networks:
      - proxy
    depends_on:
      - goaccess
    labels:
      - "traefik.enable=true"

      # main HTTP service (NGINX serving static files)
      - "traefik.http.routers.goaccess-nginx.rule=Host(`goaccess.${DOMAINNAME}`)"
      - "traefik.http.routers.goaccess-nginx.entrypoints=websecure"
      - "traefik.http.routers.goaccess-nginx.tls=true"
      - "traefik.http.routers.goaccess-nginx.tls.certresolver=le-cf"
      - "traefik.http.services.goaccess-nginx.loadbalancer.server.port=80"

  goaccess:
    image: allinurl/goaccess:latest
    container_name: goaccess
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    volumes:
      - /srv/goaccess/config/goaccess.conf:/srv/config/goaccess.conf:ro
      - /mnt/httpd/logs/access.log:/srv/logs/access.log:ro
      - goaccess-data:/srv/report/
    command: ["--no-global-config", "--config-file=/srv/config/goaccess.conf"]
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.goaccess.loadbalancer.server.port=443"
      - "traefik.http.middlewares.websocket-headers.headers.customrequestheaders.Connection=Upgrade"
      - "traefik.http.middlewares.websocket-headers.headers.customrequestheaders.Upgrade=websocket"
      - "traefik.http.middlewares.websocket-headers.headers.customrequestheaders.Host=goaccess.${DOMAINNAME}"
      - "traefik.http.routers.websocket.middlewares=websocket-headers"
    networks:
      - proxy

volumes:
  goaccess-data:

I realise they customrequestheaders are probably not needed, but I was grasping at straws :)

config/goaccess.conf

tz America/Vancouver
time-format %H:%M:%S
date-format %Y-%m-%d
log-format COMBINED
log-file /srv/logs/access.log
output /srv/report/index.html
real-time-html true
ws-url wss://goaccess.MYDOMAIN.TLD
port 443

traefik/docker-compose.yml

secrets:
  zde_token:
    file: "./secrets/zde_MYDOMAIN.TLD.token"
  zzr_token:
    file: "./secrets/zzr_MYDOMAIN.TLD.token"

services:
  traefik:
    image: "traefik:latest"
    container_name: "traefik"
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    secrets:
      - "zzr_token"
      - "zde_token"
    ports:
      - 80:80
      - 443:443
    environment:
      - CF_ZONE_API_TOKEN_FILE=/run/secrets/zzr_token
      - CF_DNS_API_TOKEN_FILE=/run/secrets/zde_token
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ${TRAEFIK_HOME}/data/acme-lecf.json:/acme-lecf.json:rw
      - ${TRAEFIK_HOME}/data/logs:/logs:rw
      - ./dynamic.yml:/dynamic.yml:ro
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.traefik-secure.entrypoints=websecure"
      - "traefik.http.routers.traefik-secure.rule=Host(`traefik.${DOMAINNAME}`)"
      - "traefik.http.routers.traefik-secure.service=api@internal"
      - "traefik.http.routers.traefik-secure.middlewares=ip-whitelist@file"

traefik/traefik.yml

global:
  checkNewVersion: true
  sendAnonymousUsage: false

api:
  dashboard: true

entryPoints:
  ssh:
    address: :22
  web:
    address: :80
    forwardedHeaders:
      trustedIPs:
        - "127.0.0.1/32"
        - "192.168.0.0/16"
        - "172.16.0.0/12"
        - "10.0.0.0/8"
    http:
      redirections:
        entrypoint:
          to: websecure
  websecure:
    address: :443
    forwardedHeaders:
      trustedIPs:
        - "127.0.0.1/32"
        - "192.168.0.0/16"
        - "172.16.0.0/12"
        - "10.0.0.0/8"
    transport:
      respondingTimeouts:
        readTimeout: 300s       # Time to read entire request (default: 60s)
        writeTimeout: 300s      # Time to write response (default: 60s)
        idleTimeout: 300s       # Time between requests (default: 180s)
    http:
      middlewares:
        - secureHeaders@file
        - nofloc@file
      tls:
        certResolver: le-cf
        domains:
          - main: MYDOMAIN.TLD
            sans:
              - "*.MYDOMAIN.TLD"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    filename: /dynamic.yml

certificatesResolvers:
  le-cf:
    acme:
      email: "USER@EXAMPLE.ORG"
      storage: acme-lecf.json
      keyType: EC384
      dnsChallenge:
        provider: cloudflare
        delayBeforeCheck: 60

log:
  level: DEBUG

and finally, the dynamic:
traefik/dynamic.yml

http:
  middlewares:
    ip-whitelist:
      ipAllowList:
        sourcerange:
          - 127.0.0.1/32
          - 192.168.0.0/16
    nofloc:
      headers:
        customResponseHeaders:
          Permissions-Policy: "interest-cohort=()"
    secureHeaders:
      headers:
        sslRedirect: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
    gitea-buffer:
      buffering:
        maxRequestBodyBytes: 1073741824   # 1GB for large uploads
        memRequestBodyBytes: 52428800     # 50MB in memory
        maxResponseBodyBytes: 1073741824  # 1GB for large downloads
        memResponseBodyBytes: 52428800    # 50MB in memory
        retryExpression: "IsNetworkError() && Attempts() <= 2"
tls:
  options:
    default:
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
      minVersion: VersionTLS12

Any help getting this sorted will be appreciated.

Thank you,
Tomasz

Okay, I figured it out. I was missing priorities and upgrade header in the Host router. Here's my fixed docker-compose.yml:

services:
  goaccess-nginx:
    image: nginx:alpine
    container_name: goaccess-nginx
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    volumes:
      - /srv/goaccess/config/nginx.conf:/etc/nginx/nginx.conf:ro
      - /srv/goaccess/public:/usr/share/nginx/html:ro
    depends_on:
      - goaccess
    labels:
      - "traefik.enable=true"

      # main HTTP service (NGINX serving static files)
      - "traefik.http.routers.goaccess-nginx.entrypoints=websecure"
      - "traefik.http.routers.goaccess-nginx.rule=Host(`goaccess.${DOMAINNAME}`)"
      - "traefik.http.routers.goaccess-nginx.tls=true"
      - "traefik.http.routers.goaccess-nginx.tls.certresolver=le-cf"
      - "traefik.http.services.goaccess-nginx.loadbalancer.server.port=80"
      - "traefik.http.routers.goaccess-nginx.priority=1"

      # Security and access control middlewares
      - "traefik.http.routers.goaccess-secure.middlewares=ip-whitelist@file"
    networks:
      - proxy

  goaccess:
    image: allinurl/goaccess:latest
    container_name: goaccess
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    volumes:
      - /srv/goaccess/config/goaccess.conf:/srv/config/goaccess.conf:ro
      - /mnt/httpd/logs/access.log:/srv/logs/access.log:ro
      - /srv/goaccess/public:/srv/report
    command: [
        "--no-global-config",
        "--config-file=/srv/config/goaccess.conf"
      ]   
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.goaccess.rule=Host(`goaccess.${DOMAINNAME}`) && Header(`Connection`, `Upgrade`)"
      - "traefik.http.routers.goaccess.entrypoints=websecure"
      - "traefik.http.routers.goaccess.tls=true"
      - "traefik.http.services.goaccess.loadbalancer.server.port=443"
      - "traefik.http.routers.goaccess.tls.certresolver=le-cf"
      - "traefik.http.routers.goaccess.priority=10"

      # Security and access control middlewares
      - "traefik.http.routers.goaccess-secure.middlewares=ip-whitelist@file"
    networks:
      - proxy

Thanks for sharing those findings!