В результате будет 2 react проекта на 1 сервере доступных по разным ссылкам
- Запустить nginx в одном контейнере
- Запустить другие проекты в других контейнерах
- Научить nginx перенаправлять запросы с разных доменов на разные проекты
- Получить ssl сертификаты для всех проектов
- Убрать www из названия сайтов с помощью nginx
Перед тем как начать, должно быть следующее
- Ubuntu сервер с открытыми портами
80
и443
. - Установленный
docker
иdocker-compose
на локальном компьютере и сервере. - Зарегистрированные доменные имена. В этом руководстве я буду использовать
proj1.com
иproj2.com
для двух проектов.- Запись
A
дляproj1.com
иproj2.com
, указывает на публичный IP адрес нашего сервера. - Запись
A
дляwww.proj1.com
иwww.proj2.com
, указывает на публичный IP адрес нашего сервера.
- Запись
- nodejs
- create-react-app
- yarn
- docker
- docker-compose
docker ps
- список запущенных контейнеровdocker network ls
- список всех активных и неактивных сетей созданных docker'омdocker system prune
- очистка всех остановленных контейнеров и неиспользуемых сетейdocker-compose down
- выполняется из корня проекта. Останавливает запущенный контейнер.
В результате будет 2 простых проекта
В любом удобном месте на локальном компьютере создаем react
проект proj1
и proj2
yarn create react-app proj1
yarn create react-app proj2
По очереди запускаем каждый из проектов yarn start
и вносим маленькие изменения, чтобы видеть отличия проектов.
Например в
src -> App.js
каждого проекта вместоEdit <code>src/App.js</code> and save to reload.
(create-react-app версия 2.1.3) напишемproj1
иproj2
соответственно.
В результате каждый проект будет в отдельном контейнере. Все действия далее описываю только для одного проекта. Их нужно сделать для двух проектов.
В корне проекта создаем Dockerfile
Dockerfile - это инструкция по созданию контейнера
FROM mhart/alpine-node:11
# Качаем node контейнер с docker hub
RUN yarn global add serve
# Устанавливаем serve для сервирования нашего проекта
WORKDIR /app
# Указываем главную папку в контейнере, в которой будет наш проект
COPY package.json yarn.lock ./
# Копируем файлы package.json и yarn.lock в папку, указанную выше (не забываем ./)
RUN yarn
# Устанавливаем зависимости
COPY . .
# Копируем остальные файлы проекта в главную папку
RUN yarn build
# Компилируем проект
CMD serve -s /app/build
# Серверуем проект из новой папки build (по умолчанию порт 5000)
Далее в корне проекта создаем docker-compose.yml
docker-compose - это инструкция по запуску контейнер(а/ов)
version: '3.7'
# Смотреть актуальную версию docker-compose синтаксиса на оф. сайте
services:
# Объявляем список сервисов
proj1:
# Указываем название сервиса
container_name: proj1
# Указываем название контейнера (не обязательно)
build: .
# Запускаем Dockerfile из корня проекта
environment:
- NODE_ENV=production
# ENV переменная production сообщит yarn, что не нужно качать зависимости для разработки, если такие имеются.
ports:
# Слева порт, который будет доступен на локальной машине. Справа - тот, к которому нужен доступ в контейнере.
- 8080:5000
# 5000 - порт который по умолчанию открывает serve в Dockerfile
В корне проекта создаем .dockerignore
.dockerignore - это список исключаемых из копирования
COPY . .
файлов и папок вDockerfile
.git
node_modules
build
Из корня проекта выполняем
docker-compose up
При первом запуске будут выполняться все инструкции
Dockerfile
, что займет какое-то время. Дальнейшие запуски, при отсутствии изменений в проекте, будут проходить быстрее.
Когда видим строку вида proj1 | INFO: Accepting connections at http://localhost:5000
, можем проверить в браузере localhost:8080
. Результатом должен быть react
проект с надписью proj1
.
В командной строке нажимаем ctrl+c
, чтобы выйти из контейнера и проверяем проект proj2
В результате будет 2 адреса, каждый из которых будет вести на
localhost
. Нужно для дальнейшей настройкиnginx
. Этот шаг можно пропустить и продолжать действия на сервере
В hosts
файле на компьютере добавляем запись
127.0.0.1 proj1.com
127.0.0.1 proj2.com
В результате будет
nginx
контейнер, который будет работать на80
порту
В удобном месте создаем папку nginx
.
Затем в ней создаем файл docker-compose.yml
version: '3.7'
services:
nginx:
container_name: nginx
image: nginx:latest
# Вместо Dockerfile берем готовый nginx (:latest - последняя версия) с docker hub
volumes:
- ./data/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- 80:80
# Порт 80 будет доступен как в контейнере, так и снаружи.
volumes - позволяет использовать файлы из локального компьютера не копируя их в контейнер. В данном случае мы берем файл
nginx.conf
из папкиdata
, который мы создадим дальше, в папкеnginx
на локальном компьютере и "говорим" контейнеру, что это файл который находится здесь/etc/nginx/conf.d/default.conf
В папке nginx
создаем папку data
в которой создаем файл nginx.conf
(название и путь должны соответствовать указанным в docker-compose
)
server {
return 301 http://google.com;
}
Самый простой
conf
, для проверки работы контейнера.
В папке nginx
выполняем docker-compose up
. В браузере при переходе на localhost
мы должны попадать на сайт google.
Останавливаем контейнер и переходим далее
В результате
nginx
контейнер сможет перенаправлять запросы в другие контейнеры. Действия описанные далее дляproj1
нужно выполнять для каждого проекта.
В папке nginx
в файле docker-compose
под блоком services
пишем новый блок networks
networks:
# Блок для объявления внутренних docker сетей, которые запустятся с этим файлом
nginx_net:
# Название сети для этого контейнера
name: nginx_net
# Название сети для внешних контейнеров
В блоке services
в подблок nginx
добавляем
networks:
nginx_net:
# Подключаемся к новой сети в этом контейнере
Результат:
version: '3.7'
services:
nginx:
container_name: nginx
image: nginx:latest
networks:
nginx_net:
volumes:
- ./data/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- 80:80
networks:
nginx_net:
name: nginx_net
В папке proj1
в файле docker-compose
удаляем
ports:
- 8080:5000
Контейнер по прежнему слушает на порту
5000
, но уже не открывает его для локального компьютера. Этот порт теперь доступен только контейнерам в той-же сети.
Под блоком services
пишем
networks:
nginx_net:
external: true
# Сообщает о том, что сеть nginx_net находится не в этом контейнере.
В блоке services
в подблок proj1
добавляем
networks:
nginx_net:
Результат:
version: '3.7'
services:
proj1:
container_name: proj1
build: .
networks:
nginx_net:
environment:
- NODE_ENV=production
networks:
nginx_net:
external: true
Удаляем все, что писали для примера и пишем
server {
server_name proj1.com;
location / {
resolver 127.0.0.11;
set $project http://proj1:5000;
proxy_pass $project;
}
}
server {
server_name proj2.com;
location / {
resolver 127.0.0.11;
set $project http://proj2:5000;
proxy_pass $project;
}
}
Каждый блок
server
отвечает за отдельные задачи.
server_name
определяет с какого именно домена пришел запрос.
location /
указывает на то, что нас устраивает какproj1.com
, так и что угодно после/
.
proxy_pass
перенаправляет запрос на наш контейнер. proj1 вhttp://proj1:5000
доступен благодаря названию контейнера вdocker-compose -> services
.
resolver 127.0.0.11;
иset $project http://proj1:5000;
нужны для того, чтобы контейнер мог запустится даже при нерабочем контейнереproj1
. Можно было написать иproxy_pass http://proj2:5000;
без переменных, но в этом случае, если не запущен контейнерproj1
,nginx
не запустится и выдаст ошибку.
Запускаем все контейнеры proj1
, proj2
и nginx
. В результате, вводя в браузере proj1.com
и proj2.com
мы попадаем на наши 2 проекта.
Если, все описанное выше работает, можно переносить наши проекты на сервер заменив все названия проектов, доменов в nginx.conf
proj1
и proj2
на необходимые названия и домены.
Для удобства я продолжу использовать proj1
и proj2
.
В nginx.conf
в server_name
добавляем www
записи. В итоге строка будет выглядеть так
server_name proj1.com www.proj1.com;
Убедившись, что на сервере все работает, можно приступать к следующей части.
Далее, много информации берется от сюда
В результате в папке
nginx
,docker-compose up
будет запускать 2 контейнера.nginx
иcertbot
Идея работы certboot проста
1) По определенному URL (/.well-known) certbot размещает данные (файл)
2) После чего обращается к серверу LetsEncrypt
3) Сервер LetsEncrypt опрашивает сайт, на котором размещен certbot с разных адресов и тем самым убеждается, что сервер тот, за кого себя выдает.
4) LetsEncrypt отдает сертификат/ключ certbot`у
Как сделать:
1) Использовать certbot в режиме webroot (не nginx)
2) В nginx прописать ./well-known чтобы указывал туда, куда certbot положит файл, который проверит LetsEncrypt
3) В nginx указать брать сертификат/ключ оттуда, куда его положит certboot
Пути к файлам для пунктов 2) и 3) - нужно указывается в параметрах certbot.
То есть куда кладет файлы certbot - оттуда берет и nginx
Сертификат LetsEncrypt действует 90 дней, поэтому нужно его обновлять (разумеется, чаще чем раз в 3 месяца)
Certbot запоминает свои настройки и обновлять чтобы не обязательно все параметры со всеми путями задавать.
И, разумеется, nginx должен быть доступен снаружи и именно по тому самуму url "/.well-known"
В services -> nginx -> ports
добавляем
- 443:443
# Открываем порты для ssl соединения
В services
добавляем
certbot:
# Новый контейнер, который запуститься вместе с nginx
container_name: certbot
image: certbot/certbot
# Образ берется с docker hub
networks:
nginx_net:
# Подключаем к той-же сети, что и остальные контейнеры
Если мы запустим контейнер сейчас, будет ошибка, так как
certbot
не может провести первичную настроку в docker контейнере.
В конце мы запустим скрипт, который сгенерирует, сначала, фейковые сертификаты, для запуска
nginx
, а затем и настоящие. В этой части мы прописываем пути по которымnginx
иcertbot
смогут их увидеть.
В services -> nginx
добавляем
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
В services -> certbot
добавляем
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
Результат
version: '3.7'
services:
nginx:
container_name: nginx
image: nginx:latest
networks:
nginx_net:
volumes:
- ./data/nginx.conf:/etc/nginx/conf.d/default.conf
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
ports:
- 80:80
- 443:443
certbot:
container_name: certbot
image: certbot/certbot
networks:
nginx_net:
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
networks:
nginx_net:
name: nginx_net
Здесь мы создаем финальную конфигурацию
nginx
. В примере будет 1server
блок отвечающий заproj1
. Соответственно, сколько проектов, столько иserver
блоков.
Существует множество конфигураций nginx
. Здесь приведена одна из них.
server {
listen 80;
listen 443 ssl;
# Слушаем на портах 80 и 443
server_name proj1.com www.proj1.com;
# Этот сервер блок выполняется при этих доменных именах
ssl_certificate /etc/letsencrypt/live/proj1.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/proj1.com/privkey.pem;
# ssl_certificate и ssl_certificate_key - необходимые сертификаты
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# include и ssl_dhparam - дополнительные, рекомендуемые Let's Encrypt, параметры
# Определяем, нужен ли редирект с www на без www'шную версию
if ($server_port = 80) { set $https_redirect 1; }
if ($host ~ '^www\.') { set $https_redirect 1; }
if ($https_redirect = 1) { return 301 https://proj1.com$request_uri; }
location /.well-known/acme-challenge/ { root /var/www/certbot; }
# Путь по которому certbot сможет проверить сервер на подлинность
location / {
resolver 127.0.0.11;
set $project http://proj1:5000;
proxy_pass $project;
}
}
В services -> nginx
добавляем
restart: unless-stopped
# Перезапустит контейнер в непредвиденных ситуациях
command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
# Перезапустит nginx контейнер каждые 6 часов и подгрузит новые сертификаты, если есть
В services -> certbot
добавляем
restart: unless-stopped
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
# Проверяет каждые 12 часов, нужны ли новые сертификаты
Результат
version: '3.7'
services:
nginx:
container_name: nginx
image: nginx:latest
restart: unless-stopped
networks:
nginx_net:
volumes:
- ./data/nginx.conf:/etc/nginx/conf.d/default.conf
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
ports:
- 80:80
- 443:443
command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
certbot:
container_name: certbot
image: certbot/certbot
restart: unless-stopped #+++
networks:
nginx_net:
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
networks:
nginx_net:
name: nginx_net
Находясь в папке nginx
вводим
curl -L https://raw.githubusercontent.com/dancheskus/nginx-docker-ssl/master/init-letsencrypt.sh > init-letsencrypt.sh
Затем открываем полученный init-letsencrypt.sh
и вписываем:
- вместо
example.com
свои домены через пробел - меняем пути, если меняли их в папке
staging
временно ставим 1 (для теста)
chmod +x init-letsencrypt.sh
sudo ./init-letsencrypt.sh
Выполнив последнюю команду в папке nginx -> data
появятся новые папки с сертификатами необходимые для работы nginx
и certbot
Если в тестовом режиме все получилось, то ставим staging=0
и повторяем процедуру.
Готово
docker-compose run --rm --entrypoint "certbot certificates" certbot
- позволяет проверить зарегистрированные сертификаты и узнать их срок годности.