
Nginx и HTTPS перед Docker Compose: как открыть приложение наружу
Публикуем Compose-приложение через Nginx на хосте: оставляем контейнер на loopback, настраиваем reverse proxy, проверяем HTTPS и закрытые порты.
Содержание
Контейнерное приложение не обязано напрямую принимать соединения из интернета. На одном VPS удобно оставить его порт доступным только по адресу 127.0.0.1, а внешние запросы принимать системным Nginx. Nginx завершает TLS-соединение, выбирает сайт по домену и передаёт запрос локальному порту приложения. Такой порядок оставляет один понятный внешний вход — 80/443.
Как будет идти запрос
Браузер соединяется с Nginx по HTTPS. Nginx проверяет домен в своём server-блоке и пересылает HTTP-запрос на 127.0.0.1:8080. Docker направляет этот порт в контейнер на порт 3000. База данных и другие внутренние сервисы остаются в сети Compose без секции ports.
Сначала должны работать отдельно две части: приложение отвечает на loopback-порту, а домен и сертификат уже настроены для Nginx. Если HTTPS ещё нет, пройдите настройку домена, Nginx и сертификата до подключения proxy.
Оставьте наружу только Nginx
В compose.yaml опубликуйте порт приложения на loopback хоста. В примере процесс внутри контейнера слушает 3000, а Nginx на хосте будет обращаться к 8080:
services:
web:
image: registry.example.com/team/app@sha256:ACTUAL_DIGEST
ports:
- "127.0.0.1:8080:3000"
restart: unless-stopped
Замените ссылку registry.example.com/...@sha256:ACTUAL_DIGEST точной ссылкой на проверенный образ. База данных и другие внутренние сервисы могут оставаться в этом же файле, но им не нужна секция ports, если к ним обращаются только контейнеры общей сети. Отсутствие публикации не заменяет пароль базы и ограниченные права её пользователя.
Перед применением посмотрите итоговую конфигурацию и затем пересоздайте только сервис приложения. Команды выполняются в каталоге Compose-проекта на VPS:
docker compose config --quiet
docker compose up -d --no-deps web
docker compose ps
В таблице ps сервис web должен иметь состояние running или healthy, если у него есть healthcheck. Ошибка address already in use означает, что порт 8080 уже занят: выясните процесс через sudo ss -lntp '( sport = :8080 )', а не останавливайте его вслепую.
Проверьте приложение без Nginx
До изменения веб-сервера отправьте запрос прямо на loopback. Так вы отделите ошибку контейнера от ошибки proxy:
curl --fail --silent --show-error --head http://127.0.0.1:8080/
docker compose logs --tail 100 web
sudo ss -lntp '( sport = :8080 )'
Успехом будет HTTP-ответ приложения и строка 127.0.0.1:8080 среди слушающих сокетов. Адрес 0.0.0.0:8080 или [::]:8080 означает более широкую публикацию, чем требуется этой схеме; исправьте ports и пересоздайте сервис.
Передайте запрос из Nginx
Создайте отдельный файл /etc/nginx/sites-available/app.example.com. Ниже показана только часть для HTTPS; строки сертификата должны уже соответствовать вашему домену и не копируются из чужого сайта:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
proxy_pass указывает локальный адрес приложения. Заголовок Host сохраняет домен запроса, X-Real-IP и X-Forwarded-For передают адрес клиента, а X-Forwarded-Proto сообщает приложению, что внешний запрос пришёл по HTTPS. Приложение должно доверять этим заголовкам только от своего proxy, иначе клиент сможет подделать их при прямом доступе.
Если сайт ещё не включён, создайте символическую ссылку в sites-enabled. Сначала убедитесь, что ссылка с таким именем отсутствует, и только затем выполните:
sudo ln -s /etc/nginx/sites-available/app.example.com \
/etc/nginx/sites-enabled/app.example.com
sudo nginx -t
sudo systemctl reload nginx
nginx -t обязан завершиться сообщением об успешной проверке. При ошибке не выполняйте reload: исправьте указанный файл и строку. Reload применяет проверенную конфигурацию без обычного разрыва активных соединений.
Проверьте весь путь по публичному адресу
Теперь запрос должен пройти через DNS, TLS, Nginx и контейнер. Выполните проверки с другого устройства или внешней системы, чтобы не ограничиваться сетью самого VPS:
curl --fail --silent --show-error --head https://app.example.com/
curl --silent --show-error --output /dev/null \
--write-out '%{http_code}\n' http://app.example.com/
Первый запрос должен вернуть успешный код приложения по HTTPS. Второй обычно получает постоянный редирект 301 или 308, если HTTP настроен только для перехода на HTTPS. Подставьте свой домен и сравнивайте код с принятой конфигурацией, а не с примером механически.
Отдельно попробуйте обратиться извне к PUBLIC_IP:8080. Соединение не должно устанавливаться, потому что Docker слушает этот порт только на loopback. Проверьте также, что firewall разрешает 80/443, но не содержит отдельного внешнего правила для 8080.
Разберите ошибку по участкам
| Симптом | Где начать проверку |
|---|---|
| loopback не отвечает | docker compose ps, healthcheck и журнал web |
| loopback отвечает, Nginx даёт 502 | адрес proxy_pass, порт и журнал ошибок Nginx |
| HTTPS не устанавливается | DNS, срок и имя сертификата, порт 443 |
| виден неверный сайт | server_name, default server и DNS-адрес |
| приложение создаёт HTTP-ссылки | доверие к X-Forwarded-Proto в приложении |
| реальный IP потерян | обработка proxy-заголовков и наличие другого CDN/proxy |
Не меняйте сразу Docker, Nginx и DNS. Сначала подтвердите ближайший работающий участок: контейнер, loopback, локальный Nginx, затем внешний адрес.
Как вернуть прежнюю схему
До изменения сохраните копию рабочего Nginx-файла и прежний compose.yaml в Git. Если новый proxy не работает, верните прежний файл, выполните sudo nginx -t и только после успешной проверки сделайте reload. Затем восстановите прежнюю публикацию порта в Compose и пересоздайте только web.
Для более подробной настройки заголовков используйте руководство по reverse proxy. Поведение сетей и портов разобрано в отдельном уроке Docker, а проверку результата следует закрепить smoke-тестом.
Источники
Самопроверка
Проверьте, что материал усвоен
Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.
Разбираем коротко
Частые вопросы
Нужно ли помещать Nginx в тот же Compose-проект?
Нет. Для одного VPS понятна схема, в которой системный Nginx принимает 80/443, а Compose-приложение слушает только loopback. Контейнерный Nginx тоже возможен, но требует отдельного решения для сертификатов и публикации портов.
Почему приложение привязывают к 127.0.0.1?
Так опубликованный порт доступен процессам на самом хосте, включая Nginx, но не принимает прямые соединения на внешнем интерфейсе VPS.
Нужно ли открывать порт приложения в UFW?
Нет, если Nginx обращается к нему через 127.0.0.1. Снаружи нужны только SSH по принятой политике и HTTP/HTTPS для веб-сервера.


