Как подружить docker, localhost и HTTPS
Когда разрабатываешь веб-сервис, который крутится на проде по HTTPS, есть большой соблазн не запариваться и работать локально по HTTP. Потому что так проще, да и в целом же всё работает плюс-минус одинаково. Если сервис простой, то скорее всего никаких проблем не всплывёт, но если сервис не монолитный и зависит от внешних сервисов или сам предоставляет АПИ клиентам, то проблемы с CORS неизбежны. И обычно эти проблемы обнаруживаются слишком поздно — либо на стейджинге, либо даже после релиза на продакшен.
Браузер начинает ругаться, что запросы с HTTPS на HTTP или наоборот не безопасны, сторонние сервисы перестают отвечать, так как по HTTP работали только тестовые стенды, с которыми вы и работали.
Решение простое — нужно не лениться и максимально приблизить локальную среду к боевой. Благо это задача из серии один раз сделал и используешь для всех последующих проектов.
Docker на продакшене
Если у вас продакшен без Docker, то вам поможет статья — Как получить бесплатный SSL-сертификат и установить его на Nginx.
В этой статье я рассматриваю только локальную сборку через docker-compose, потому что на продакшене, как ни странно, это немного проще. Достаточно выполнить понятную инструкцию по настройке Certbot и/или добавить соответствующий докер-образ с Docker Hub.
Docker на localhost
См. как запустить сайт через docker-compose на примере докеризации Эгеи.
На локалхосте Certbot не подойдёт, так как ему нужно реально существующее доменное имя и доступный через интернет сайт. Поэтому прописать локальный адрес в /etc/hosts не поможет. Решение — выпустить свой самоподписанный сертификат. Да, он не будет приниматься никем, кроме нашей системы, но для локальной разработки этого и не нужно.
Создаём доменное имя
Во-первых, создадим локально доменное имя, чтобы оно было похожим на боевое, а не обычный и скучный localhost. Например, docker.loc для основного сайта и api.docker.loc для API:
$ sudo vim /etc/hosts
127.0.0.1 localhost
127.0.0.1 docker.loc
127.0.0.1 api.docker.loc
...
Если для вашего сервиса необходимо несколько доменов, то пропишите их ниже по аналогии.
Выпускаем свой SSL-сертификат
Для реальных сайтов SSL-сертификаты выдаются специальными сертификационными центрами за деньги. За это они потом могут подтвердить верность домена по запросу любого браузера. Мы же выпустим свой сертификат и сами его подтвердим.
SSL-сертификат создаётся одной командой и состоит из двух частей: публичного сертификата и секретного ключа.
openssl req -x509 -out docker.loc.crt -keyout docker.loc.key \
-newkey rsa:2048 -nodes -sha256 \
-subj '/CN=docker.loc' -extensions EXT -config <( \
printf "[dn]\nCN=docker.loc\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:docker.loc\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
Файлы docker.loc.crt и docker.loc.key будут сохранены в той папке, откуда вы запускаете команду.
Добавляем сертификаты в ОС
Ниже пример, как это сделать на Mac OS X.
- Найдите сертификат docker.loc.crt в Finder (или можно в нужной папке консоли ввести «open .»)
- Дважды кликните по нему, появится окно.
- Введите пароль администратора
- Теперь нужно найти этот сертификат в Keychain Access и выбрать Trust Always
Добавляем сертификаты в docker-compose сборку
Сертификаты нужно положить в контейнер с веб-сервером. Я приведу пример для Nginx и сборки по типу https://github.com/pluseg/e2-docker.
Расположение сертификатов и конфигов
В этой сборке папки из app/docker/nginx монтируются прямо в nginx-контейнер. Поэтому удобно расположить файлы так:
<project root>
- app
--- docker
----- nginx
------- conf.d
--------- docker.loc.conf
------- ssl
--------- docker.loc.crt
--------- docker.loc.key
Пример docker.loc.conf (проект на PHP + Symfony)
# лучше с php соединять через socket
upstream php-upstream { server web:9000; }
# Редиректим HTTP на HTTPS
server {
listen 80;
listen [::]:80;
server_name docker.loc;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
root /var/www/html/docs;
server_name docker.loc;
ssl_certificate /etc/nginx/ssl/docker.loc.crt;
ssl_certificate_key /etc/nginx/ssl/docker.loc.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/(index|app|app_dev|config)\.php(/|$) {
fastcgi_pass php-upstream;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_read_timeout 300;
}
error_log /var/log/nginx/docker.loc-error.log;
access_log /var/log/nginx/docker.loc-access.log;
}
Пример docker-compose.yml
Добавляем 443 порт и несколько volume с конфигами и сертификатами.
version: '3.2'
services:
...
nginx:
image: nginx:latest
ports:
- 80:80
- 443:443
volumes:
- ./app/docker/nginx/conf.d:/etc/nginx/conf.d
- ./app/docker/nginx/ssl:/etc/nginx/ssl
- ./:/var/www/html
- ./var/logs/nginx:/var/log/nginx
command: /bin/bash -c "exec nginx -g 'daemon off;'"
Запускаем и проверяем
docker-compose up -d --build