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