Cockpit behind traefik

Hi there,

I'm trying to access cockpit through traefik v2.3.2.

The whole setup is running on a VPS.

Traefik is running in docker-compose, using the traefik network.
I have multiple docker containers running flawlessly together with traefik.

Cockpit is running outside docker on localhost:9090
If i open the firewall to 9090, i can access cockpit with serverIP:9090

I would like to route cockpit through traefik to access via subdomain and handle https for me.

So my plan is to block 9090 with ufw and have traefik route to cockpit.

My problem is accessing a non-docker service on the same host.
My dynamic config gets picked up, and the router gets created, but the routing doesn't go through.
Depending on what i try i get errors 404, 502 or 504

  • Using network_mode: host prevents me from using the traefik network.
  • I cannot bind the traefik container on 9090 because the cockpit service is using that already

ufw config

     To       Action      From
     --       ------      ----
[ 1] 22      ALLOW IN    Anywhere                   # ssh
[ 2] 80      ALLOW IN    Anywhere                   # http
[ 3] 443     ALLOW IN    Anywhere                   # https

traefik docker-compose.yml

version: "3.3"

networks:
  traefik:
    external: true

services:
  traefik:
    image: traefik:v2.3.2
    restart: unless-stopped
    container_name: ${TRAEFIK_GUI_SUBDOMAIN}
    env_file:
      - .env
    ports:
      - 80:80
      - 443:443

      # Binding port 9090 does not work because cockpit is occupying that port
      #- 9090:9090

    # Trying network mode host stops me from using the traefik network
    # network_mode: host

    # using extra hosts also does not solve my problem
    # extra_hosts:
      #- "dockerhost:PUBLIC_SERVER_IP" # i would be surprised if that worked

    volumes:
      - ./data/acme.json:/acme.json # <== for certs
      - /var/run/docker.sock:/var/run/docker.sock # <== for docker access
      - ./config/traefik.yml:/etc/traefik/traefik.yml # static config file
      - ./config/rules:/etc/traefik/rules # dynamic config folder
      - ./config/users:/etc/traefik/users # users file
    networks:
      - traefik
    labels:
      # API / Dashboard
      - traefik.enable=true
      - traefik.http.routers.traefik.service=api@internal
      - traefik.http.routers.traefik.entrypoints=websecure
      - traefik.http.routers.traefik.rule=Host(`${TRAEFIK_GUI_SUBDOMAIN}.${DOMAIN}`)
      - traefik.http.routers.traefik.middlewares=simple_auth@file # enable auth

traefik.yml

log:
  level: ERROR #INFO #INFO #DEBUG, INFO, WARN, ERROR, FATAL, PANIC

api:
  insecure: false
  debug: false
  dashboard: true

global:
  sendAnonymousUsage: true

certificatesResolvers:
  cloudflare:
    acme:
      storage: /acme.json
      email: MY_EMAIL
      dnschallenge:
        provider: cloudflare
        resolvers: 1.1.1.1:53,1.0.0.1:53
        # caserver: https://acme-staging-v02.api.letsencrypt.org/directory # Staging Server

entryPoints:
  web:
    address: :80
  websecure:
    address: :443
    http:
      tls:
        certresolver: cloudflare

providers:
  docker:
    exposedbydefault: false
    network: traefik
    endpoint: unix:///var/run/docker.sock

  file:
    directory: /etc/traefik/rules/
    watch: true

Inside /etc/traefik/rules i have:

auth.yml (works)

http:
  middlewares:
    simple_auth:
      basicauth:
        headerField: X-WebAuth-User
        usersFile: /etc/traefik/users

http-redirect.yml (works)

http:
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https
        permanent: true

  # dummy service for https redirect, the URL will be never called
  services:
    noop:
      loadBalancer:
        servers:
          - url: "http://192.168.0.1"

  routers:
    http-catchall:
      rule: "hostregexp(`{host:[a-z-.]+}`)"
      entrypoints:
        - web
      middlewares:
        - redirect-to-https
      service: noop

cockpit.yml (works, but the routing doesn't)

http:
  routers:
    cockpit:
      rule: "Host(`{{ env "COCKPIT_SUBDOMAIN" }}.{{ env "DOMAIN" }}`)"
      entryPoints:
        - websecure
      middlewares:
        - simple_auth
      service: cockpit
      # tls:
      #   certResolver: cloudflare

  services:
    cockpit:
      loadBalancer:
      # healthCheck shows always good, but that's a problem for another time
        healthCheck:
          interval: "5s"
          timeout: "3s"
        servers:
          # These are all the different urls i tried to route
          # - url: "http://host.docker.internal:9090"
          # - url: "http://172.17.0.1:9090"
          # - url: "http://localhost:9090"
          # - url: "http://SERVERS_PUBLIC_IP:9090" # no surprise here
          # - url: "https://dockerhost:9090" # together with extra_hosts => dockerhost:PUBLIC_SERVER_IP on the traefik container, also didn't expect this to work
          # - url: "http://host.docker.internal:9090"
          # - url: "http://172.17.0.1:9090"
          - url: "http://172.18.0.1:9090" # this is the ip from traefik network, but also no success

I also set the config for cockpit to allow reverse proxying

[WebService]
Origins = http://cockpit.mydomain.com ws://cockpit.mydomain.com https://cockpit.mydomain.com wss://cockpit.mydomain.com
ProtocolHeader = X-Forwarded-Proto
AllowUnencrypted=true

I'm pretty new to traefik and tried this now for a few days with no success. Can you give me any pointers on how i can get that working?

If you want to use host.docker.internal, you have to define this extra host in your traefik compose service:

extra_hosts:
  - "host.docker.internal:host-gateway"

PS: Available since Docker 20.10 (https://github.com/moby/moby/pull/40007)

First of all, for me cockpit is HTTPS, so try https://*:9090. Afterwards you also want to make sure Traefik trusts the CA from cockpit, which is probably randomly generated.

I managed to get it to work but the inline frame is showing up like this:

when I open inline frame in new window it is opening fine.

Hi @vitachaos ,

How did you do to make it works?

Thank you in advance.

To access external URLs with Traefik, you need to declare a service with loadbalancer.servers.url in a dynamic config file, which needs to be loaded with providers.file in static config. (Doc)

When using Docker, note that you can not use 127.0.0.1, as that would be the containers internal localhost, not the one of your host. In that case you would need to use the external IP of the host. host.docker.internal is usually just available on "Docker Desktop".

1 Like

Howdy,

I managed to solve this problem thanks to a few other resources and finally this thread!

In my case I had:

  • cockpit-ws (provided alongside my Centos 9) on the host
  • traefik v3 running in Docker as a container

The goal is to make cockpit-ws operate in non-ssl or HTTP only mode and have it only accessible on localhost and to docker. Then we let traefik talk to cockpit-ws to show it on my domain at cockpit.example.com via HTTPS only.

This is the setup (create any files that don't already exist).

/etc/systemd/system/cockpit.socket.d/listen.conf

[Socket]
ListenStream=
ListenStream=127.0.0.1:9090
ListenStream=172.17.0.1:9090
FreeBind=yes

From the TCP Port and Address docs, we make cockpit only accessible on localhost (127.0.0.1) and docker (172.17.0.1) .

Don't use 0.0.0.0 because then you're exposing it to the internet with HTTP on your servers IP!

After modifying this file, run:

sudo systemctl daemon-reload
sudo systemctl restart cockpit.socket

/etc/cockpit/cockpit.conf

[WebService]
Origins = http://cockpit.example.com ws://cockpit.example.com https://cockpit.example.com wss://cockpit.example.com
ProtocolHeader = X-Forwarded-Proto
AllowUnencrypted=true

Replace the example subdomain with your own. Origins and ProtocolHeader are required to allow a successful connection post login. Cockpit enforces a HTTPS requirement for non local host addresses so we allow HTTP by setting AllowUnencrypted.

After modifying this file, run:

sudo systemctl restart cockpit

traefik/compose.yml

services:
  traefik:
    image: traefik:v3.1
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik_static.yml:/etc/traefik/traefik.yml
      - ./dynamic:/etc/traefik/dynamic
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - web
    restart: unless-stopped

networks:
  web:
    external: true

Set extra_hosts to bind host.docker.internal to host-gateway.
Note: If you don't want to do this you can also use the 172.17.0.1 directly to refer to the host from inside docker containers but this is not recommended.

traefik_dynamic.yml

# Cockpit setup
http:
  routers:
    cockpit:
      rule: "Host(`cockpit.example.com`)"
      entrypoints:
        - https
      tls: true
      service: cockpit-service

  services:
    cockpit-service:
      loadBalancer:
        servers:
          - url: "http://host.docker.internal:9090"

Add the above to your dynamic config. Replace the example subdomain with your own. Don't forget to setup the tls certificates as you normally would for this subdomain (not included in above config).

Note: You can use 172.17.0.1 here if you didn't want to use extra_hosts in previous step.


Test everything

Redeploy traefik on docker if needed, then test.
Make sure cockpit is working from your subdomain and localhost via curl. :slight_smile:

1 Like