Docker-сервисы, которые принимают HTTP-запросы, обычно слушают внутренний порт контейнера: например, 3000, 8080, 5000 или 8000. Этот порт используется внутри Docker-сети, а внешний доступ к сервису лучше организовать через домен, HTTPS и reverse proxy.

В production-среде пользователь открывает сервис по адресу:

https://app.example.com

Домен указывает на публичный IP VPS, Traefik принимает запрос и передаёт его во внутренний Docker-сервис.

Схема выглядит так:

User → https://app.example.com → Traefik → Docker service

1. Направьте домен на VPS

Сначала создайте DNS-запись для домена или поддомена.

Пример:

Type: A
Name: app
Value: SERVER_IP

Если домен example.com, сервис будет открываться по адресу:

app.example.com

Проверить DNS можно командой:

dig app.example.com +short

Ожидаемый результат:

SERVER_IP

2. Подготовьте Docker-сервис

Допустим, приложение работает внутри контейнера на порту 3000.

Пример базового сервиса:

services:
 app:
   image: your-app-image:latest
   container_name: app
   restart: unless-stopped
   expose:
     - "3000"

Здесь используется expose, а не ports, потому что приложение не нужно открывать напрямую в интернет. Оно будет доступно только внутри Docker-сети для Traefik.

Плохо:

ports:
  - "3000:3000"

Лучше:

expose:
  - "3000"

Важно
Не публикуйте внутренний порт приложения наружу, если доступ к нему должен идти только через HTTPS и reverse proxy.

3. Добавьте Traefik как reverse proxy

Ниже пример docker-compose.yml, где Traefik публикует приложение через HTTPS.

Замените:

app.example.com
admin@example.com
your-app-image:latest

на свои значения.

services:
 traefik:
   image: traefik:v3.0
   container_name: traefik
   restart: unless-stopped
   command:
     - "--providers.docker=true"
     - "--providers.docker.exposedbydefault=false"

     - "--entrypoints.web.address=:80"
     - "--entrypoints.websecure.address=:443"

     - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
     - "--entrypoints.web.http.redirections.entrypoint.scheme=https"

     - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
     - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
     - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
     - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
   ports:
     - "80:80"
     - "443:443"
   volumes:
     - "/var/run/docker.sock:/var/run/docker.sock:ro"
     - "./letsencrypt:/letsencrypt"
   networks:
     - proxy

 app:
   image: your-app-image:latest
   container_name: app
   restart: unless-stopped
   expose:
     - "3000"
   labels:
     - "traefik.enable=true"

     - "traefik.http.routers.app.rule=Host(`app.example.com`)"
     - "traefik.http.routers.app.entrypoints=websecure"
     - "traefik.http.routers.app.tls=true"
     - "traefik.http.routers.app.tls.certresolver=letsencrypt"

     - "traefik.http.services.app.loadbalancer.server.port=3000"
   networks:
     - proxy

networks:
 proxy:
   name: proxy

Traefik использует routers, services и entrypoints для обработки HTTP-запросов. В Docker-сценарии эти параметры обычно задаются через labels контейнера. 

4. Что делает эта конфигурация

В этом примере Traefik:

  • слушает порты 80 и 443;
  • получает информацию о контейнерах через Docker socket;
  • не публикует все контейнеры автоматически;
  • ищет только контейнеры с traefik.enable=true;
  • принимает запросы на app.example.com;
  • выпускает HTTPS-сертификат через Let’s Encrypt;
  • проксирует запросы на контейнер app:3000.

 

Ключевой блок для приложения:

labels:
 - "traefik.enable=true"
 - "traefik.http.routers.app.rule=Host(`app.example.com`)"
 - "traefik.http.routers.app.entrypoints=websecure"
 - "traefik.http.routers.app.tls=true"
 - "traefik.http.routers.app.tls.certresolver=letsencrypt"
  - "traefik.http.services.app.loadbalancer.server.port=3000"

Что это означает:

traefik.enable=true

Traefik должен обработать этот контейнер.

Host(`app.example.com`)

Запросы на этот домен направляются в контейнер.

entrypoints=websecure

Сервис работает через HTTPS entrypoint, то есть через порт 443.

tls.certresolver=letsencrypt

Для этого домена нужно получить сертификат через Let’s Encrypt.

loadbalancer.server.port=3000

Внутренний порт приложения внутри контейнера — 3000.

Traefik поддерживает ACME/Let’s Encrypt для автоматического получения сертификатов. В официальном примере Traefik для Docker Compose с Let’s Encrypt показана схема, где сертификат выпускается через HTTP challenge для сервиса, опубликованного через Traefik.

5. Запустите сервисы

Создайте папку для сертификатов:

mkdir -p letsencrypt

Запустите контейнеры:

docker compose up -d

Проверьте статус:

docker ps

Ожидаемо должны быть запущены:

traefik
app

Проверьте логи Traefik:

docker logs traefik

Если DNS настроен правильно и порты 80/443 доступны, Traefik сможет выпустить HTTPS-сертификат.

6. Откройте порты в firewall

На VPS должны быть открыты порты:

80/tcp
443/tcp

Если используется UFW:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

SSH лучше оставить доступным только с доверенного IP:

sudo ufw allow from YOUR_TRUSTED_IP to any port 22 proto tcp

7. Проверьте работу HTTPS

Откройте в браузере:

https://app.example.com

Также можно проверить через curl:

curl -I https://app.example.com

Ожидаемый результат:

HTTP/2 200

или другой успешный ответ приложения.

Если открыть:

http://app.example.com

Traefik должен перенаправить запрос на HTTPS, потому что в конфигурации включён redirect с web на websecure:

- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"

Итог

Для публикации Docker-сервиса через HTTPS на VPS можно использовать Traefik как reverse proxy.

В простом варианте схема выглядит так:

Domain → VPS → Traefik → Docker service

Приложение не нужно открывать напрямую через:

SERVER_IP:PORT

Достаточно оставить его внутри Docker-сети, а наружу публиковать только 80 и 443 через Traefik.

Traefik удобен для Docker-сервисов, потому что маршрутизация описывается прямо в docker-compose.yml через labels. Это особенно полезно, если на одном VPS нужно публиковать несколько сервисов:

app.example.com → app:3000
api.example.com → api:8080
panel.example.com → panel:9000

Такой подход безопаснее и удобнее для production-схемы: пользователь открывает сервис по домену с HTTPS, а внутренний порт приложения остаётся скрытым.