Условие
Программист Тирекс написал праздничное веб-приложение с обратным отсчетом до Нового года и хочет поздравить им всех коллег. Приложение уже собрано: в директории web находятся готовые статические артефакты (HTML, JavaScript и изображения). У Тирекса есть TLS-сертификат и приватный ключ, и он хочет, чтобы приложение работало по HTTPS.
Задача
Нужно упаковать приложение в Docker-контейнер, чтобы его можно было легко запускать на любом сервере, и сделать доступным из интернета. Времени у Тирекса осталось совсем немного!
Создайте конфигурацию nginx, которая:
- слушает порт 80 и выполняет 301-редирект на HTTPS (https://$host$request_uri);
- слушает порт 443 с включенным SSL;
- использует сертификат /etc/nginx/ssl/cert.pem и ключ /etc/nginx/ssl/key.pem;
- отдает статические файлы из /usr/share/nginx/html по пути /.
Напишите Dockerfile, который:
- копирует в контейнер конфигурацию nginx и артефакты приложения
- создает пустую директорию /etc/nginx/ssl (для монтирования сертификатов при запуске);
- использует легкий образ (например, nginx:alpine).
При запуске контейнера должны быть опубликованы порты 80 и 443.
Бонусная задача
Добавьте docker-compose.yml файл, чтобы запускать приложение одной короткой командой из папки с сертификатами.
Решение
Перед решением задачи рекомендуем ознакомиться со следующими статьями:
Решением подобных кейсов обычно занимается DevOps-инженер. Это специалист, который автоматизирует и синхронизирует весь цикл разработки ПО от кода до релиза. Он отвечает за непрерывную интеграцию и доставку (CI/CD), управление инфраструктурой (IaC), мониторинг, масштабирование и обеспечение надежной работы продукта.
Предложенные примеры команд выполняются в Linux и MacOS. Для создания контейнера в Windows процесс похож, но отличаются команды генерации сертификата и редактирования файлов.
В первую очередь, нужно скачать архив, создать папку проекта и распаковать в нее все файлы. Флаг -p позволяет создавать вложенные каталоги:
mkdir -p happy/app
tar -xzvf newyear_countdown_web.tar.gz -C happy/app
Далее перейдем в папку с проектом. В данном примере — happy. Создадим в ней необходимые дополнительные директории: для файла конфигурации nginx и для сертификатов.
mkdir ssl
mkdir nginx
Создадим сертификат для локальной отладки приложения:
openssl req -x509 -nodes -days 30 -newkey rsa:2048 \
-keyout ssl/key.pem \
-out ssl/cert.pem \
-subj "/C=RU/ST=SPb/L=city/O=MyTask/CN=$(hostname)"
Далее создадим файл nginx/default.conf. Это можно сделать как в редакторе кода, так и открыв текстовый редактор nano, например, командой nano ./nginx/default.conf.
В файле конфигурации требуется добавить два блока server. Первый обрабатывает запросы по незащищенному соединению и возвращает код HTTP 301 — постоянный редирект.
server {
listen 80;
return 301 https://$host$request_uri;
}
Следующий блок server уже обеспечивает безопасное соединение. Он использует ранее созданные сертификат и ключ, а также указывает браузеру использовать безопасные протоколы TLS версии 1.2 и выше. Внутри этого блока прописан раздел location — он определяет, как веб-сервер будет отвечать на запросы к сайту (какую страницу отдавать при обращении по доменному имени). Здесь указывается путь к статическим файлам, задается главная страница index.html, а также правило для случая, когда запрашиваемый файл не найден — возвращается ошибка 404.
server {
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ =404;
}
}
Созданные файлы и папки необходимо поместить в контейнер. Для этого создаем Dockerfile следующего содержания:
FROM nginx:alpine
# Копируем статику
COPY app/ /usr/share/nginx/html/
# Копируем конфигурацию
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
# Создаём папку для SSL
RUN mkdir -p /etc/nginx/ssl
# Открываем порты
EXPOSE 80 443
Теперь создадим сам контейнер и запустим его. Для этого убедимся в правильном составе файлов и папок:
tree -L 2
.
├── Dockerfile
├── app
│ ├── assets
│ ├── ...
│ └── version.json
├── nginx
│ └── default.conf
└── ssl
├── cert.pem
└── key.pem
И выполним сборку и просмотр существующих образов:
docker build -t countdown-app .
docker images
Будет примерно следующий вывод (IMAGE ID уникальный для каждой сборки):

Далее можно выполнить запуск. С помощью флага -d мы позволяем контейнеру работать «в фоне», флаг --name задает имя приложению для удобного отслеживания. При помощи -p указывается, какими портами будет «светить» этот контейнер приложения (в данном примере — 80 и 443). Флаг -v отвечает за монтирование директории ssl в режиме ro = read only.
docker run -d \
--name countdown \
-p 80:80 \
-p 443:443 \
-v "$(pwd)/ssl:/etc/nginx/ssl:ro" \
countdown-app
С помощью команды docker ps можете убедиться в корректной работе.
А как убедиться в корректной работе контейнера? Достаточно в той же консоли ввести curl localhost:80 -v - обращение к localhost по порту 80. Вывод будет примерно следующим:
...
< HTTP/1.1 301 Moved Permanently
...
< Location: https://localhost/
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.29.4</center>
</body>
</html>
Наше приложение полностью корректно говорит браузеру, что оно перемещено на https://localhost/ (т. е. добавлен префикс https). Однако при попытке обратиться по curl https://localhost мы получим сообщение: «curl: (60) SSL certificate problem: self signed certificate». Для проверки работоспособности страницы откройте в браузере https://localhost и согласитесь с небезопасным подключением. После этого веб-страница откроется.

Дополнительно можно прописать docker-compose файл. Он позволяет запускать одно или несколько приложений и их экземпляров всего общей короткой командой. Для этого создайте файл docker-compose.yaml следующего содержания:
version: '3.8'
services:
countdown-app:
build: .
ports:
- "80:80"
- "443:443"
volumes:
# Монтируем локальную папку с сертификатами в контейнер
- ./ssl:/etc/nginx/ssl:ro
Здесь указано создание сервиса countdown-app. При запуске дана инструкция «собрать все в текущей директории» (т. е. Dockerfile), выведены порты 80 и 443 и подключены директории с сертификатами.