В этом упражнении мы потренируемся доставлять приложение без реального сервера и платного облака.
Идея: один участник пары собирает Docker-образ и публикует его в Docker Registry, а второй участник скачивает этот образ на своём ноутбуке и запускает приложение. Второй участник не использует исходный код приложения: для него Docker-образ становится готовым артефактом деплоя.
Установи Docker. На Windows можно использовать Docker Desktop, для него также понадобится WSL.
Проверь, что Docker работает:
docker --version
docker run hello-worldДля основного задания понадобится аккаунт в Docker Hub: https://hub.docker.com/
Если Docker Hub недоступен или не получается войти, используй запасной вариант из раздела 7. Передача образа без Docker Registry.
Разделитесь на роли:
- Участник A: собирает и публикует Docker-образ.
- Участник B: скачивает опубликованный образ и запускает приложение на своём ноутбуке.
После прохождения упражнения поменяйтесь ролями.
Важное ограничение: участник B не должен запускать npm install, открывать папку deploy-app или собирать приложение из исходного кода. Он получает только имя Docker-образа и команду запуска.
Основа сборки образа - Dockerfile. В нём описываются шаги подготовки окружения для запуска приложения. Подробнее можно изучить в документации Dockerfile.
Создай в корне репозитория файл Dockerfile и вставь в него:
FROM node:20-alpine
COPY deploy-app /app
WORKDIR /app
RUN npm ci
ENV PORT=3000
EXPOSE 3000
CMD ["npm", "start"]Что здесь происходит:
FROMзадаёт базовый образ. Здесь используется Node.js 20 на Alpine Linux.COPYкопирует приложение из папкиdeploy-appвнутрь образа.WORKDIRзадаёт рабочую директорию для следующих команд.RUN npm ciустанавливает зависимости поpackage-lock.json.ENV PORT=3000задаёт порт приложения по умолчанию.EXPOSE 3000документирует, какой порт слушает контейнер.CMDзадаёт команду запуска приложения.
Команды выполняет участник A.
Собери образ:
docker build -t deploy-app:1.0.0 .Запусти контейнер:
docker run --rm -p 3000:3000 deploy-app:1.0.0Открой в браузере:
http://localhost:3000
Если приложение открылось, останови контейнер через Ctrl+C.
Команды выполняет участник A.
Войди в Docker Hub:
docker loginЗатегируй образ. Вместо dockerhub_username подставь свой логин в Docker Hub:
docker tag deploy-app:1.0.0 dockerhub_username/deploy-app:1.0.0Опубликуй образ:
docker push dockerhub_username/deploy-app:1.0.0После успешной публикации передай участнику B только имя образа:
dockerhub_username/deploy-app:1.0.0
Команды выполняет участник B.
Скачай образ. Вместо dockerhub_username используй логин участника A:
docker pull dockerhub_username/deploy-app:1.0.0Запусти контейнер:
docker run --rm -p 3000:3000 dockerhub_username/deploy-app:1.0.0Открой в браузере:
http://localhost:3000
Если приложение открылось, деплой через Docker-образ выполнен успешно.
Теперь участник A должен изменить приложение и выпустить новую версию образа.
Например, можно изменить текст на главной странице в файле deploy-app/routes/index.js.
После изменения собери новую версию:
docker build -t deploy-app:1.0.1 .Затегируй и опубликуй её:
docker tag deploy-app:1.0.1 dockerhub_username/deploy-app:1.0.1
docker push dockerhub_username/deploy-app:1.0.1Участник B скачивает и запускает новую версию:
docker pull dockerhub_username/deploy-app:1.0.1
docker run --rm -p 3000:3000 dockerhub_username/deploy-app:1.0.1Проверьте, что в браузере видна обновлённая версия приложения.
Этот вариант нужен, если Docker Hub недоступен или нет аккаунта.
Участник A сохраняет образ в файл:
docker build -t deploy-app:1.0.0 .
docker save deploy-app:1.0.0 -o deploy-app.tarПосле этого файл deploy-app.tar нужно передать участнику B любым удобным способом.
Участник B загружает образ из файла:
docker load -i deploy-app.tarПроверяет, что образ появился локально:
docker imagesЗапускает приложение:
docker run --rm -p 3000:3000 deploy-app:1.0.0Этот способ хуже похож на реальный деплой, потому что образ передаётся файлом. Но он всё равно показывает главный принцип: приложение можно доставить и запустить без установки зависимостей вручную.
В реальных проектах образ обычно собирается не на ноутбуке разработчика, а в CI/CD.
В этом задании GitHub Actions будет автоматически собирать Docker-образ и публиковать его в GitHub Container Registry.
Создай файл .github/workflows/publish-image.yml:
name: Publish Docker image
on:
push:
branches:
- main
permissions:
contents: read
packages: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare image name
id: image
run: echo "name=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/deploy-app:${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.image.outputs.name }}Сделай commit и push в GitHub. После успешного выполнения workflow образ появится в Packages.
Чтобы второй участник смог скачать образ без авторизации, package нужно сделать публичным в настройках GitHub Packages.
После этого участник B сможет выполнить:
docker pull ghcr.io/github_username/deploy-app:commit_sha
docker run --rm -p 3000:3000 ghcr.io/github_username/deploy-app:commit_shaОтветь на вопросы после выполнения задания:
- Чем Docker-образ отличается от контейнера?
- Почему участнику B не нужно устанавливать Node.js и зависимости проекта?
- Зачем образу нужен тег?
- Что изменится, если опубликовать новую версию с тем же тегом
latest? - Чем передача через Docker Registry лучше передачи файла
deploy-app.tar? - Что в этом упражнении является аналогом деплоя на сервер?
В конце упражнения у пары должно быть:
Dockerfileв корне репозитория;- локально собранный Docker-образ;
- опубликованный образ в Docker Hub или GHCR;
- приложение, запущенное на втором ноутбуке без исходного кода;
- понимание, что Docker-образ можно использовать как переносимый артефакт деплоя.