Port :8089/:4443 утечка в admin URLs закрыта. URL Rewrite 2.1 + apphost allowedServerVariables (HTTPS/SERVER_PORT/SERVER_PORT_SECURE) + Web.config rule на X-Forwarded-Proto=https → set SERVER_PORT=443/SERVER_PORT_SECURE=1/HTTPS=on ДО того как ASP.NET читает их в Url.SiteRoot(). Лечит все 11 cms (общий site snolla). Path B (traefik http entrypoint :80→:8090) abandoned — Docker Desktop WSL2 NAT quirk на host.docker.internal:80 возвращает 17-byte 301 plain text независимо от traefik internals. Quirk не reproducible на Linux Docker (synology). Side regression: pre-existing customErrors mode="off" lowercase в Web.config пробудился после ASP.NET full-reload (мой rewrite-block edit) — fixed (Off). Outside scope, open: emspb.snolla.com /admin/assets/<guid>/getList → 500 NullRef в AssetsJsonViewModelBuilder.cs:22 (model null от GetFolderByPath). User подтвердил «только этот site». Зафиксировано в snapshot open issue #8. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.0 KiB
title, type, tags, sources, updated
| title | type | tags | sources | updated | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Traefik на Windows Docker Desktop — нюансы | concept |
|
|
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 :80 → SERVER_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.