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, а внутрішній порт застосунку залишається прихованим.