3:00 ночи. PagerDuty будит алертом SSL_EXPIRED. Ты открываешь терминал и видишь, что сертификат на продакшене истёк три дня назад. Причина проста: полгода назад ты настроил Certbot вручную, а потом забыл про него. Или ситуация хуже: ты деплоишь новый микросервис, браузеры блокируют запросы из-за mixed content, потому что в конфиге Nginx для нового домена нет HTTPS.
Ручное управление сертификатами в среде, где контейнеры живут днями или часами, — это технический долг. Он копится незаметно, а взрывается в момент пиковой нагрузки или при аудите безопасности.
Твоя задача: любой новый контейнер, поднятый через docker-compose, должен автоматически получать валидный Let’s Encrypt сертификат и становиться доступен по HTTPS без твоего участия. Не править конфиги, не перезагружать Nginx, не бегать с cron-скриптами.
Эта статья показывает, как настроить связку nginx-proxy и letsencrypt-companion. После настройки добавление нового сервиса сводится к двум переменным окружения в файле compose.
Почему классический Nginx не подходит для Docker
В классической установке Nginx конфигурационные файлы хранятся в /etc/nginx/sites-available [18]. При изменении настроек нужно обновлять файлы и применять изменения. В статичной инфраструктуре это норма. В Docker-среде, где сервисы могут подниматься и падать динамически, ручное редактирование конфигов становится узким горлом.
Образ jwilder/nginx-proxy:alpine решает эту проблему иначе. При запуске ему передают доступ к Docker Socket (/var/run/docker.sock) в режиме только для чтения через volume [3]. Прокси считывает события API Docker. Когда появляется новый контейнер с определёнными метками, он генерирует нужный конфигурационный файл и применяет его. Тебе не нужно заходить внутрь контейнера или трогать файлы на хосте.
Для корректной работы прокси и подключаемых сервисов используют внешнюю Docker-сеть с именем nginx-proxy [2]. Это разделяет инфраструктурный слой (проксирование, SSL) и бизнес-логику (твои приложения). Контейнеры общаются между собой по внутренним именам сервисов, внешние порты открыты только у самого прокси.
Как работает автоматический SSL внутри оркестрации
Чтобы получить сертификат для сервиса внутри сети, нужен агент, который живёт рядом с прокси. Образ jrcs/letsencrypt-nginx-proxy-companion выполняет эту роль [1]. Он следит за событиями в Docker API. Когда появляется контейнер с переменной LETSENCRYPT_HOST, компаньон инициирует процесс проверки владения доменом через HTTP-01 challenge [12].
Для работы компаньону нужны две вещи:
- Переменная окружения
NGINX_PROXY_CONTAINERсо значениемnginx-proxy(имя контейнера прокси) [4]. - Доступ на запись в том
certs, который смонтирован в/etc/nginx/certsвнутри контейнера прокси [5]. Именно здесь хранятся полученные ключи и цепочки сертификатов [14].
После получения сертификата трафик с порта 80 автоматически перенаправляется на 443 [13].
Подключение сервиса: две переменные вместо конфига
Бизнес-контейнер (твой сайт, API или админка) не должен знать о существовании Nginx или SSL. Его задача — слушать свой порт и отвечать на запросы. Вся магия происходит на уровне сети и переменных окружения.
Чтобы сервис попал за прокси, выполни три условия:
- Подключи сервис к внешней сети. В
docker-compose.ymlтвоего сервиса укажи сетьnginx-proxy[6]. Если этой сети нет, создай её командойdocker network create nginx-proxy[2]. - Скрой прямые порты. Используй директиву
exposeвместоports[11].exposeделает порт видимым для других контейнеров в той же сети, но не пробрасывает его на хост. Это предотвращает доступ к сервису в обход прокси и SSL. - Добавь переменные окружения. Это единственный конфиг, который тебе нужно менять для каждого нового домена:
VIRTUAL_HOST=example.com— указывает, по какому домену прокси должен направлять запросы [7].LETSENCRYPT_HOST=example.com— запускает процесс получения сертификата для этого домена [9].LETSENCRYPT_EMAIL=admin@example.com— адрес для уведомлений от Let’s Encrypt (просрочка, ошибки) [10].
Если сервису нужно слушать нестандартный порт (не 80), добавь переменную VIRTUAL_PORT [8]. Например, если твоё приложение внутри контейнера слушает 3000, укажи VIRTUAL_PORT=3000.
Чек-лист «Zero-Touch SSL» на сегодня
Не откладывай настройку инфраструктуры. Потрать 15 минут сейчас, чтобы не чинить продакшен ночью.
- Создай изолированную сеть:
- Запусти прокси и компаньона. Сохрани этот блок как
infra/docker-compose.yml:
Запусти:docker compose up -d. - Подключи свой сервис. В
docker-compose.ymlтвоего приложения добавь: - Проверь результат. Выполни команду:
Ответ должен содержатьHTTP/2 200и заголовки с валидным сертификатом. Если видишь502 Bad Gateway— проверь, что приложение внутри контейнера действительно слушает порт 8080 и доступно из сетиnginx-proxy.
Правило: Если контейнера нет в сети nginx-proxy или у него нет переменных VIRTUAL_HOST и LETSENCRYPT_HOST, он не получит сертификат и не будет доступен извне. Никаких исключений.