Traefik + Swarm + Gunicorn + Django not working redirect to root path

Hi all! The second week I can not solve the problem with accessibility through the proxy to django service. if you go to dev.domain.com/admin after 20 seconds of spinning the page, the django admin panel opens, but if you go to / then there should be an instant redirect by means of django itself to the page /accounts/login/ since the user is not logged in. after a couple of minutes the redirect happens but the page is not displayed.

I thought the problem was in the docker, but no, if this docker is run simply through the docker run service, it works as it should. If you put sour in the form of nginx between traefik and django, then it will not work either. I think the problem is still in the traefik settings. Tell me pls what else you can check to understand the problem and fix it?

traefik-docker-compose.yaml

version: '3.9'

services:
  traefik:
    image: traefik:latest
    env_file:
      - .env.traefik
    ports:
      - mode: host
        target: 80
        published: 80
      - mode: host
        target: 443
        published: 443
      - mode: ingress
        target: 8082
        published: 8082
      - mode: ingress
        target: 8080
        published: 8080
    security_opt:
      - no-new-privileges:true
    deploy:
      placement:
        constraints:
          - node.labels.traefik-public.traefik-public-certificates == true
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.constraint-label=traefik-public

        - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
        - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
        - traefik.http.middlewares.my-basic-auth.basicauth.users=user:$f2h02flkfs
        
        - traefik.http.services.traefik.loadbalancer.server.port=8080
        - traefik.http.routers.traefik-http.rule=Host(`traefik.domain.com`)
        - traefik.http.routers.traefik-http.entrypoints=http
        - traefik.http.routers.traefik-http.service=traefik
        - traefik.http.routers.traefik-http.middlewares=https-redirect

        - traefik.http.routers.traefik-https.rule=Host(`traefik.domain.com`)
        - traefik.http.routers.traefik-https.entrypoints=websecure
        - traefik.http.routers.traefik-https.tls=true
        - traefik.http.routers.traefik-https.tls.certresolver=cloudflare
        - traefik.http.routers.traefik-https.tls.domains[0].main=domain.com
        - traefik.http.routers.traefik-https.tls.domains[0].sans=*.domain.com
        - traefik.http.routers.traefik-https.service=api@internal

        - traefik.http.services.prometheus.loadbalancer.server.port=8082
        - traefik.http.routers.prometheus.rule=PathPrefix(`/metrics`)
        - traefik.http.routers.prometheus.service=prometheus
        - traefik.http.routers.prometheus.entrypoints=metrics
        - traefik.http.routers.prometheus.middlewares=my-basic-auth
        
      restart_policy:
        condition: any
        delay: 5s
        max_attempts: 3
        window: 120s
      update_config:
        delay: 10s
        order: start-first
        parallelism: 1
      rollback_config:
        parallelism: 0
        order: stop-first
    logging:
      driver: json-file
      options:
        'max-size': '10m'
        'max-file': '5'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/logs/:/var/log/traefik
      - /etc/localtime:/etc/localtime:ro
      - ./traefik/conf.yml:/etc/traefik/dynamic_conf/conf.yml:ro
      - ./traefik/certs/:/etc/letsencrypt/
      - traefik-acme:/etc/traefik/acme/
    command:
      # Enable Docker in Traefik, so that it reads labels from Docker services
      - --providers.docker
      # Add a constraint to only use services with the label "traefik.constraint-label="
      - --providers.docker.constraints=Label(`traefik.constraint-label`, `traefik-public`)
      # Do not expose all Docker services, only the ones explicitly exposed
      - --providers.docker.exposedbydefault=false
      # Enable Docker Swarm mode
      - --providers.docker.swarmmode
      - --providers.file.directory=/etc/traefik/dynamic_conf/
      - --providers.file.watch=true
      - --providers.docker.network=traefik-public
      - --entrypoints.websecure.address=:443
      - --entrypoints.http.address=:80
      - --accesslog
      - --log.level=DEBUG
      - --log.filePath=/var/log/traefik/traefik.log
      # Writing Logs to a File, in JSON
#      - --log.filePath=/path/to/traefik.log
#      - --log.format=json
#      - --accesslog=true
#      - --accesslog.filepath=/var/log/traefik/access.log
      # Configuring a buffer of 100 lines
#      - --accesslog.filepath=/var/log/traefik/access.log
#      - --accesslog.bufferingsize=100
      # Enable the Dashboard and API
      - --api.insecure=true
      - --ping.entryPoint=http
      - --ping.entryPoint=websecure
      - --metrics.prometheus=true
      - --metrics.prometheus.addEntryPointsLabels=true
      - --metrics.prometheus.addServicesLabels=true
      - --metrics.prometheus.addrouterslabels=true
      - --entryPoints.metrics.address=:8082
      - --metrics.prometheus.entryPoint=metrics
#      - --metrics.prometheus.manualrouting=true
      # LetsEncrypt
      - --certificatesresolvers.cloudflare.acme.email=username@gmail.com
      - --certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare
      - --certificatesresolvers.cloudflare.acme.storage=/etc/traefik/acme/acme.json

      - --entryPoints.web.forwardedHeaders.insecure
      - --entryPoints.websecure.forwardedHeaders.insecure
    
    networks:
      - traefik-public

volumes:
  traefik-acme:
    driver: local

networks:
  traefik-public:
    external: true

traefik conf.yml


api:
  log:
    level: DEBUG  # DEBUG, INFO, WARNING, ERROR, CRITICAL
    format: common  # common, json, logfmt
    filePath: /var/log/traefik/traefik.log

  accessLog:
    format: common  # common, json, logfmt
    filePath: /var/log/traefik/access.log

    dashboard: true
    debug: true
#  entryPoints:
#    http:
#      address: ":80"
#    https:
#      address: ":443"
#  serversTransport:
#    insecureSkipVerify: true
#  providers:
#    docker:
#      network: traefik-public
#      endpoint: "unix:///var/run/docker.sock"
#      exposedByDefault: false
#    file:
#      filename: /etc/traefik/dynamic_conf/conf.yml
  certificatesResolvers:
    cloudflare:
      acme:
        email: username@gmail.com
        storage: /etc/traefik/acme/acme.json
        dnsChallenge:
          provider: cloudflare
          resolvers:
            - "1.1.1.1:53"
            - "1.0.0.1:53"

django-compose.yaml

version: '3'

services:
  web:
    image: registry.lan.diarynchy.com/diarynchy-app:dev
#    ports:
#      - 8000:8000
    command: >
      bash -c 'gunicorn --env DJANGO_SETTINGS_MODULE=webapp.settings.dev webapp.wsgi:application --log-level=debug --timeout=600 --workers 4 --threads 2 --bind=127.0.0.1:5000'
    volumes:
      - webapp_audio_dev:/webapp/audio/
      - static_volume_dev:/webapp/static/
      - media_volume_dev:/webapp/media/
    env_file:
      - .env
    networks:
      - traefik-public
    deploy:
      placement:
        constraints:
          - node.labels.dev == true
      restart_policy:
        condition: on-failure
      resources:
        reservations:
          cpus: "1.5"
          memory: 2G
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.constraint-label=traefik-public

        - traefik.http.routers.django-dev-http.rule=Host(`dev.domain.com`)
        - traefik.http.routers.django-dev-http.entrypoints=http
        - traefik.http.routers.django-dev-http.middlewares=https-redirect

        - traefik.http.routers.django-dev-https.rule=Host(`dev.domain.com`)
        - traefik.http.routers.django-dev-https.entrypoints=websecure
        - traefik.http.routers.django-dev-https.tls=true
        - traefik.http.routers.django-dev-https.tls.certresolver=cloudflare

        - traefik.http.services.django-dev.loadbalancer.server.port=5000

volumes:
  static_volume_dev:
  webapp_audio_dev:
  media_volume_dev:

networks:
  traefik-public:
    external: true

When I go to dev.domain.com/admin/login/ - all good. Open wery fast.
But if i go to dev.domain.com/ - this spinning long time

In django, on / this route working function which redirect to /accounts/login if you not authenticated

def index(request):
    if request.user.is_authenticated:

        context = {
        }
        return render(request, 'recorder.html', context)
    else:
        return HttpResponseRedirect('/accounts/login/')

If go to /accounts/login/ directly problem the same
this controler

class LogInView(GuestOnlyView, FormView):
    template_name = 'accounts/login.html'

    @staticmethod
    def get_form_class(**kwargs):
        if settings.DISABLE_USERNAME or settings.LOGIN_VIA_EMAIL:
            return SignInViaEmailForm

        if settings.LOGIN_VIA_EMAIL_OR_USERNAME:
            return SignInViaEmailOrUsernameForm

        return SignInViaUsernameForm

    @method_decorator(sensitive_post_parameters('password'))
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        # Sets a test cookie to make sure the user has cookies enabled
        request.session.set_test_cookie()

        return super(LogInView, self).dispatch(request, *args, **kwargs)

Log gunicorn in docker container

graceful_timeout: 30
  keepalive: 2
  limit_request_line: 4094
  limit_request_fields: 100
  limit_request_field_size: 8190
  reload: False
  reload_engine: auto
  reload_extra_files: []
  spew: False
  check_config: False
  print_config: False
  preload_app: False
  sendfile: None
  reuse_port: False
  chdir: /webapp
  daemon: False
  raw_env: ['DJANGO_SETTINGS_MODULE=webapp.settings.dev']
  pidfile: None
  worker_tmp_dir: None

0
  group: 0
  umask: 0
  initgroups: False
  tmp_upload_dir: None
  secure_scheme_headers: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
  forwarded_allow_ips: ['127.0.0.1']
  accesslog: None
  disable_redirect_access_to_syslog: False
  access_log_format: %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
  errorlog: -
  loglevel: debug
  capture_output: False
  logger_class: gunicorn.glogging.Logger
  logconfig: None
  logconfig_dict: {}
  syslog_addr: udp://localhost:514
  syslog: False
  syslog_prefix: None
  syslog_facility: user
  enable_stdio_inheritance: False
  statsd_host: None
  dogstatsd_tags: 
  statsd_prefix: 
  proc_name: None
  default_proc_name: webapp.wsgi:application
  pythonpath: None
  paste: None
  on_starting: <function OnStarting.on_starting at 0x7ff7085ca4c0>
  on_reload: <function OnReload.on_reload at 0x7ff7085ca5e0>
  when_ready: <function WhenReady.when_ready at 0x7ff7085ca700>
  pre_fork: <function Prefork.pre_fork at 0x7ff7085ca820>
  post_fork: <function Postfork.post_fork at 0x7ff7085ca940>
  post_worker_init: <function PostWorkerInit.post_worker_init at 0x7ff7085caa60>
  worker_int: <function WorkerInt.worker_int at 0x7ff7085cab80>
  worker_abort: <function WorkerAbort.worker_abort at 0x7ff7085caca0>
  pre_exec: <function PreExec.pre_exec at 0x7ff7085cadc0>
  pre_request: <function PreRequest.pre_request at 0x7ff7085caee0>
  post_request: <function PostRequest.post_request at 0x7ff7085caf70>
  child_exit: <function ChildExit.child_exit at 0x7ff7085af0d0>
  worker_exit: <function WorkerExit.worker_exit at 0x7ff7085af1f0>
  nworkers_changed: <function NumWorkersChanged.nworkers_changed at 0x7ff7085af310>
  on_exit: <function OnExit.on_exit at 0x7ff7085af430>
  proxy_protocol: False
  proxy_allow_ips: ['127.0.0.1']
  keyfile: None
  certfile: None
  ssl_version: 2
  cert_reqs: 0
  ca_certs: None
  suppress_ragged_eofs: True
  do_handshake_on_connect: False
  ciphers: None
  raw_paste_global_conf: []
  strip_header_spaces: False
[2023-06-16 14:49:33 +0000] [1] [INFO] Starting gunicorn 20.1.0
[2023-06-16 14:49:33 +0000] [1] [DEBUG] Arbiter booted
[2023-06-16 14:49:33 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
[2023-06-16 14:49:33 +0000] [1] [INFO] Using worker: gthread
[2023-06-16 14:49:33 +0000] [7] [INFO] Booting worker with pid: 7
[2023-06-16 14:49:34 +0000] [8] [INFO] Booting worker with pid: 8
[2023-06-16 14:49:34 +0000] [9] [INFO] Booting worker with pid: 9
[2023-06-16 14:49:34 +0000] [10] [INFO] Booting worker with pid: 10
[2023-06-16 14:49:34 +0000] [1] [DEBUG] 4 workers
Not Found: /static/admin/js/nav_sidebar.js
Not Found: /static/admin/css/nav_sidebar.css
Not Found: /static/admin/css/dark_mode.css
Not Found: /static/admin/css/login.css
Not Found: /static/admin/css/base.css
Not Found: /static/admin/css/responsive.css
Not Found: /static/admin/css/base.css
Not Found: /static/admin/css/login.css
Not Found: /static/admin/css/nav_sidebar.css
Not Found: /static/admin/css/dark_mode.css
Not Found: /static/admin/css/responsive.css
Not Found: /static/admin/css/dark_mode.css
Not Found: /static/admin/css/base.css
Not Found: /static/admin/js/nav_sidebar.js
Not Found: /static/admin/css/login.css
Not Found: /static/admin/css/nav_sidebar.css
Not Found: /static/admin/css/responsive.css

I commented like this and page starts working!

class LogInView(GuestOnlyView, FormView):
    template_name = 'accounts/login.html'

    @staticmethod
    def get_form_class(**kwargs):
        if settings.DISABLE_USERNAME or settings.LOGIN_VIA_EMAIL:
            return SignInViaEmailForm

        if settings.LOGIN_VIA_EMAIL_OR_USERNAME:
            return SignInViaEmailOrUsernameForm

        return SignInViaUsernameForm

    # @method_decorator(sensitive_post_parameters('password'))
    # @method_decorator(csrf_protect)
    # @method_decorator(never_cache)
    # def dispatch(self, request, *args, **kwargs):
    #     # Sets a test cookie to make sure the user has cookies enabled
    #     request.session.set_test_cookie()
    #
    #     return super(LogInView, self).dispatch(request, *args, **kwargs)

But i think we missing after proxy some header (for example csrf) maybe

Found what part made issue

class LogInView(GuestOnlyView, FormView):
    template_name = 'accounts/login.html'

    @staticmethod
    def get_form_class(**kwargs):
        if settings.DISABLE_USERNAME or settings.LOGIN_VIA_EMAIL:
            return SignInViaEmailForm

        if settings.LOGIN_VIA_EMAIL_OR_USERNAME:
            return SignInViaEmailOrUsernameForm

        return SignInViaUsernameForm

    @method_decorator(sensitive_post_parameters('password'))
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        # Sets a test cookie to make sure the user has cookies enabled
        # request.session.set_test_cookie()

        return super(LogInView, self).dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        request = self.request

        # If the test cookie worked, go ahead and delete it since its no longer needed
        if request.session.test_cookie_worked():
            request.session.delete_test_cookie()

        # The default Django's "remember me" lifetime is 2 weeks and can be changed by modifying
        # the SESSION_COOKIE_AGE settings' option.
        if settings.USE_REMEMBER_ME:
            if not form.cleaned_data['remember_me']:
                request.session.set_expiry(0)

        login(request, form.user_cache)

        redirect_to = request.POST.get(REDIRECT_FIELD_NAME, request.GET.get(REDIRECT_FIELD_NAME))
        # url_is_safe = is_safe_url(redirect_to, allowed_hosts=request.get_host(), require_https=request.is_secure())
        #
        # if url_is_safe:
        #     return redirect(redirect_to)


        return redirect(settings.LOGIN_REDIRECT_URL)

Like that working but I commented '# request.session.set_test_cookie()'
It looks like the cookies disappear when going through the traefik.

How to make traefik proxy requests as is?

Not sure if Traefik reads your static config file, see doc and check Traefik debug log.

You can only use one static config, either via command or via traefik.yml.

You need to enable entrypoints, otherwise Traefik is not listening.

Certresolvers are a root element, not spaced in.

Maybe check the simple Traefik example. For Traefik in Docker Swarm you need to enable Swarm (--providers.docker.swarmMode=true), should set a placement constraints (- node.role == manager ) and put the labels below deploy (as you already have). (Doc)

1 Like