Files
admin/.wiki/concepts/traefik-on-windows-docker-desktop.md

8.0 KiB
Raw Permalink Blame History

title, type, tags, sources, updated
title type tags sources updated
Traefik на Windows Docker Desktop — нюансы concept
traefik
docker
windows
letsencrypt
reverse-proxy
../sources/nas-recovery-session-2026-05-18.md
2026-05-19

Traefik на Windows Docker Desktop

Запуск traefik 2.6.6 на Windows Docker Desktop (WSL2 backend) с импортированным production-конфигом и сертификатами — пара ловушек.

Pitfall 1: traefik.yml не загружается автоматом

Symptom: traefik стартует, но routers из data/custom/*.yml ругаются на "non-existent resolver: letsEncrypt", сертификаты не отдаются.

Reason: traefik 2.x при отсутствии --configFile= ищет в дефолтных путях (/etc/traefik/, ./traefik.yml), но Linux-контейнер с CWD=/ и bind-mount ./data/traefik.yml:/traefik.yml — почему-то не подхватывает.

Fix: явно указать в compose command::

command:
  - "--configFile=/traefik.yml"
  - "--log.level=DEBUG"

Подтверждение в логах: Configuration loaded from file: /traefik.yml.

Pitfall 2: acme.json permissions через bind-mount

Symptom: traefik ругается permissions 777 for /letsencrypt/acme.json are too open, please use 600 и отключает letsEncrypt resolver (даже если у него есть валидные certs).

Reason: bind-mount Windows-файла в Linux-контейнере всегда показывает permissions 0777. Изменить через chmod нельзя — bind-mount не транслирует POSIX-perms на Windows-side.

Fix: named volume вместо bind для /letsencrypt:

volumes:
  - "traefik_letsencrypt:/letsencrypt"  # вместо ./letsencrypt:/letsencrypt
  - "./data/traefik.yml:/traefik.yml:ro"
  - "./data/custom/:/custom/:ro"

# и снизу:
volumes:
  traefik_letsencrypt:

Population volume единоразово:

docker volume create traefik_traefik_letsencrypt
docker run --rm \
  -v traefik_traefik_letsencrypt:/dest \
  -v <local-path>/traefik/letsencrypt:/src:ro \
  alpine sh -c "cp /src/acme.json /dest/acme.json && cp /src/acme.old.json /dest/acme.old.json && chmod 600 /dest/*"

Pitfall 3: docker.sock provider не работает

Symptom:

Failed to retrieve information of the docker client and server host: Error response from daemon: 
providerName=docker

С -v /var/run/docker.sock:/var/run/docker.sock:ro сокет монтируется (видно srw-rw---- 1 root root внутри контейнера), но соединение с daemon обрывается.

Reason: Docker Desktop на Windows транслирует docker.sock через WSL2 layer. Иногда permission-моэль клиента (libdocker) не принимает то, что предоставляет Desktop's proxy.

Workaround: полностью отключить docker-provider, использовать только file-provider для всех routes.

Маршруты, которые в производстве были как traefik labels на контейнерах (minio, elasticsearch, imgproxy с traefik.http.routers...labels), переписываются в data/custom/<name>.yml руками:

# data/custom/minio.yml
http:
  routers:
    minio:
      entryPoints: [https]
      rule: Host(`minio.kzntsv.site`)
      tls:
        certResolver: letsEncrypt
      service: minio
  services:
    minio:
      loadBalancer:
        servers:
          - url: http://minio:9000   # docker DNS name (работает потому что все на одной network=proxy)

Подобно для elasticsearch (с basicAuth middleware), imgproxy (→ imgproxy-nginx:80), etc.

Pitfall 4: file-provider не подхватывает изменения через bind-mount

traefik.yml имеет providers.file.watch: true, но на Windows bind-mount inotify не работает через WSL2-слой. Изменения в data/custom/*.yml не подхватываются автоматически.

Workaround: docker compose restart traefik после изменений в custom/. Несколько секунд downtime, ничего страшного.

Production порты vs нашa конфигурация

Production traefik compose биндил на host:

ports:
  - "8000:80"     # router forwards public 80 → host 8000 → container 80
  - "4443:443"
  - "8080:8080"

То есть роутер делает port-translation 80→8000, 443→4443. Не стандартные порты, но работает.

На Windows-хосте оставили те же порты (8000/4443) — не конфликтуют с локальным IIS на 80 (который не используется, но не выключен). И не требуют admin для bind.

host.docker.internal

Из traefik-контейнера достучаться до VM (которая в VBox NAT, не в docker network) — через host.docker.internal:

servers:
  - url: http://host.docker.internal:18080/   # 18080 = VBox NAT-forwarded port → VM:80

Docker Desktop резолвит host.docker.internal в IP хоста (обычно 172.x.x.1 из docker bridge perspective). Дальше Windows-host обрабатывает 18080 → VBox NAT → VM:80 → IIS → CMS.

Pitfall 5: X-Forwarded не используется ASP.NET — port utечка в admin URLs (RESOLVED 2026-05-19)

Симптом: CMS-admin генерирует URLs с internal IIS portом (:8089 после attempt 2; ранее :4443 от traefik) → browser HSTS auto-upgrade ломает TLS на нестандартном портe → admin SPA broken.

Root cause: Traefik 2.x шлёт X-Forwarded-Proto: https / X-Forwarded-Host / X-Forwarded-Port по-default (когда entrypoint https). НО CMS-код (MoreThenCms.Admin\Mis\Web\Mvc\UrlHelpers\UrlHelpers.cs) читает socket-level SERVER_PORT / SERVER_PORT_SECURE, игнорируя X-Forwarded-* headers. На VM работало случайно потому что IIS binding :80SERVER_PORT=80 стрипалось whitelist'ом {80,443} в коде.

Fix: URL Rewrite 2.1 + <serverVariables> rule на host IIS — переписывает HTTPS/SERVER_PORT/SERVER_PORT_SECURE на основе X-Forwarded-Proto. Детально в cms-server-port-leak-fix.

Path B (поменять traefik http entrypoint :80 inside container чтобы IIS-binding :80 не получал loop от Docker NAT) — не работает на Windows Docker Desktop из-за WSL2 NAT quirk, см. там же.

DNS-01 challenge через REGRU

traefik.yml имеет HTTP-01 challenge:

certificatesResolvers:
  letsEncrypt:
    acme:
      email: vitya.kuznetsov@gmail.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: http

В compose env уже:

REGRU_USERNAME=OpeItcLoc03
REGRU_PASSWORD=ytyYqC%u%QAJ

Эти креды для DNS-01 через REGRU API. Просто закомментировать httpChallenge и активировать dnsChallenge в traefik.yml:

certificatesResolvers:
  letsEncrypt:
    acme:
      email: ...
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: regru

DNS-01 более надёжный (не требует public:80 reachable для validation) и работает даже если HTTP-01 challenge не пройдёт (например, домен временно на другой хостинге).

Текущие 40 LE-сертификатов из acme.json валидны ~3 месяца (LE default). Когда подойдут к истечению — пора переключать на DNS-01.

Связано: snolla-recovery-vm, windows-recovery-host, recovery-architecture-snapshot.