В результате будет 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
В 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- позволяет проверить зарегистрированные сертификаты и узнать их срок годности.