Продолжаем знакомиться с Portainer и сферами его применения.
В двух прошлых статьях:
Первая: «Быстрый деплой бота (и не только) на Docker-хостинге с Portainer»
Вторая: «Один Portainer, чтоб править всеми»
Если вы здесь впервые: Portainer — это веб-панель, которая упрощает работу с Docker (и Docker Swarm/Kubernetes): запуск контейнеров, обновления, сети, тома и права доступа — всё в одном интерфейсе.
Мы уже разобрались:
что такое Portainer;
что за сервис DockerHosting.ru и как он помогает быстро стартовать;
как писать
Dockerfile
иdocker-compose.yml
;как развернуть проект из Git-репозитория;
как подключать к Portainer удалённые серверы и управлять на них контейнерами Docker.
В этой статье разберём, как встроить Portainer в процессы CI/CD и где хранить Docker-образы, а также узнаем, как бесплатно получить и активировать Portainer Business Edition.
Коротко для тех, кто только знакомится с темой:
CI (Continuous Integration) — автоматическая сборка и проверка кода при каждом изменении.
CD (Continuous Delivery/Deployment) — автоматическая доставка/развёртывание собранного приложения.
Регистр образов (registry) — хранилище Docker-образов (публичное или приватное), из которого Portainer и ваши сервера забирают готовые образы для деплоя.
Также рекомендую к прочтению «CI/CD: основы написания Workflow», чтобы глубже понять понятия CI/CD и Workflow.
Где хранить Docker-образы
Начнём с хранилищ (Docker Registry). Dockerfile
описывает, как собрать образ, а запись build: .
в docker-compose.yml
собирает его локально.
Однако в реальных проектах удобнее другой способ: собрать образ один раз и отправить его в реестр. Тогда при деплое сервер просто выполнит docker pull
и заберёт готовый образ. Такой подход идеально подходит для CI/CD: вся тяжёлая сборка выполняется в конвейере, а на продакшене остаётся лишь запуск контейнера.
Какие бывают реестры
Docker Hub — самое популярное публичное хранилище.
Self-hosted решения, например Harbor — реестр, который вы разворачиваете на своём сервере.
Реестры у git-хостингов: GitHub Container Registry (ghcr.io), GitLab Container Registry, встроенные решения в Gitea и другие.
Чем они отличаются
-
Docker Hub
Плюсы: легко начать, много готовых публичных образов.
Минусы: ограничения на бесплатных планах (скорость и объём хранения), зависимость от внешнего сервиса.
-
Self-hosted (например, Harbor)
Плюсы: полный контроль над хранилищем, приватность, собственные политики безопасности, возможность репликации.
Минусы: нужно администрирование, дополнительные ресурсы и поддержка.
-
Git-хостинги (GHCR/GitLab и др.)
Плюсы: образы хранятся рядом с кодом, удобно настраивать доступ, хорошая интеграция с CI/CD.
Минусы: привязка к конкретной платформе и её лимитам.
Как это работает
Собираете образ из
Dockerfile
— локально или в CI/CD.-
Присваиваете тег:
фиксированный (например, версия
1.2.3
или SHA коммита),плавающий (
latest
). Хорошая практика — использовать оба тега: фиксированный для надёжности иlatest
для быстрого запуска.
Отправляете образ в реестр (
docker push
работает примерно какgit push
, только вместо кода загружается образ).На сервере запускаете
docker-compose.yml
и вместоbuild: .
указываетеimage:
. Для продакшена безопаснее использовать конкретный тег или дайджест (@sha256:…
), а не толькоlatest
.
Я, например, использую свой git-сервер на базе Gitea для хранения и кода, и Docker-образов. Но в этой статье мы будем работать с реестром от GitHub — GHCR (ghcr.io).
Если хотите подробнее про self-hosted-хранилища — напишите в комментариях, и я подготовлю отдельный материал.
Проект для демонстрации
Для примера возьмём небольшой проект с GitHub: https://github.com/proDreams/tempProject.
Это Telegram-бот, который отвечает на любое сообщение текстом «Hello World!».
Он нам нужен, чтобы показать все шаги на реальном проекте, а вы потом сможете легко адаптировать их под свой.
Разберём, что уже есть в репозитории.
Dockerfile
Dockerfile
выглядит так:
FROM python:3.13-slim
WORKDIR /code
COPY requirements.txt /code
RUN pip install --upgrade pip && pip install -r requirements.txt
COPY . /code
CMD [ "python", "./main.py" ]
Что здесь происходит шаг за шагом:
FROM python:3.13-slim
Используем официальный базовый образ с Python 3.13 на облегчённой системе (slim
). Такой образ весит меньше и быстрее скачивается.WORKDIR /code
Устанавливаем рабочую директорию внутри контейнера. Все команды ниже будут выполняться именно в этой папке.COPY requirements.txt /code
Копируем список зависимостей. Это сделано отдельным шагом, чтобы Docker мог закешировать установку пакетов и не ставил их заново при каждой пересборке.RUN pip install --upgrade pip && pip install -r requirements.txt
Обновляемpip
и устанавливаем зависимости изrequirements.txt
. Благодаря предыдущему шагу, еслиrequirements.txt
не изменился, этот этап будет пропущен (Docker возьмёт данные из кеша).COPY . /code
Копируем остальной код проекта в контейнер.CMD ["python", "./
main.py
"]
Задаём команду, которая будет выполняться при запуске контейнера — запуск нашего Telegram-бота. Важно:CMD
можно переопределить при запуске (docker run ...
), а вотENTRYPOINT
заменить сложнее.
Этот Dockerfile
уже подходит для нашего примера, менять его не нужно.
docker-compose.yaml
Сейчас файл docker-compose.yaml
выглядит так:
services:
test-bot:
build: .
container_name: test-bot
environment:
- BOT_TOKEN=${BOT_TOKEN}
Разберём, что здесь происходит:
Создаётся сервис
test-bot
, для которого Docker собирает образ из текущей папки (build: .
) поDockerfile
.Контейнер получает понятное имя
test-bot
. Это облегчает поиск в списке контейнеров, просмотр логов и выполнение команд.-
В контейнер передаётся переменная окружения
BOT_TOKEN
. Её значение берётся:либо из переменной окружения на хосте,
либо из файла
.env
, если он находится рядом сdocker-compose.yaml
.
Совет: файл
.env
удобно использовать для хранения токенов и паролей.
В репозиторий его обычно не добавляют, а делают.env.example
с пустыми значениями, чтобы другим было понятно, какие переменные нужны.
Позже, когда мы подключим CI/CD, строчку build: .
заменим на image: ...
, чтобы использовать заранее собранный и загруженный в реестр образ.
main.py
Код бота:
import asyncio
import os
from aiogram import Dispatcher, Bot
from aiogram.types import Message
from dotenv import load_dotenv
load_dotenv()
async def send_message(message: Message) -> None:
await message.answer(text="Hello World!")
async def start() -> None:
bot = Bot(token=os.getenv('BOT_TOKEN'))
dp = Dispatcher()
dp.message.register(send_message)
try:
await dp.start_polling(bot)
finally:
await bot.session.close()
if __name__ "__main__":
asyncio.run(start())
Что здесь происходит:
load_dotenv()
Загружает переменные окружения из файла.env
. Это нужно только при запуске локально. В Docker Compose переменные подтягиваются автоматически из секцииenvironment
или черезenv_file
. То есть в контейнере этот вызов необязателен, но и не мешает.Обработчик сообщений Функция
send_message
отвечает на любое входящее сообщение фразой «Hello World!».-
Функция
start()
создаёт объект
Bot
с токеном из переменнойBOT_TOKEN
;инициализирует
Dispatcher
;регистрирует обработчик для всех сообщений (
dp.message.register(send_message)
).
Запуск long polling
dp.start_polling(bot)
включает механизм long polling — бот регулярно опрашивает серверы Telegram на наличие новых сообщений.Корректное завершение работы В блоке
finally
закрывается HTTP-сессия бота, чтобы не оставалось «висячих» соединений.Запуск из файла
if name "__main__":
asyncio.run
(start())
гарантирует, что бот запустится только если вы запускаете этот файл напрямую, а не импортируете его в другой модуль.
Workflow сборки образа
Теперь перейдём к практике. Наша цель — настроить Workflow, который будет собирать Docker-образ проекта и отправлять его в реестр GitHub.
Для этого воспользуемся GitHub Actions — встроенным в GitHub сервисом для CI/CD. Он позволяет автоматически запускать сценарии при каждом пуше в репозиторий (или по другим событиям).
Структура будет такой:
В корне проекта создаём директорию
.github
— здесь хранится всё, что связано с настройками GitHub.Внутри неё создаём папку
workflows
— именно здесь GitHub Actions ищет сценарии для запуска.В этой папке создаём файл
build_and_deploy.yaml
. В нём мы опишем задачи по сборке и деплою проекта. Для начала сделаем только сборку.
build_and_deploy.yaml
Файл Workflow целиком:
name: Build and Deploy Project
on:
push:
branches:
- main
permissions:
packages: write
contents: read
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
cache-from: type=registry,ref=ghcr.io/prodreams/tempproject:latest
cache-to: type=inline
tags: |
ghcr.io/prodreams/tempproject:latest
ghcr.io/prodreams/tempproject:${{ github.sha }}
Что здесь происходит
name
Просто название Workflow. Оно отображается во вкладке Actions на GitHub.on.push.branches: main
Запускаем Workflow при каждом пуше в ветку main. Если вы работаете в другой ветке для продакшена, заменитеmain
на нужную (например,production
).-
permissions
Даём встроенномуGITHUB_TOKEN
права:packages: write
— загружать образы в GitHub Container Registry,contents: read
— читать код из репозитория.
runs-on: ubuntu-latest
GitHub запускает сборку на Linux-раннере (виртуальной машине Ubuntu).Checkout Забираем код вашего репозитория на раннер. Без этого Docker не увидит
Dockerfile
и исходники.Buildx Подключаем расширенный механизм сборки Docker. Он поддерживает кеширование и мультиархитектуру (например, если вы захотите собирать образы под ARM).
-
Login в GHCR Авторизуемся в GitHub Container Registry (
ghcr.io
) с помощью встроенногоGITHUB_TOKEN
.
Если при пуше в организацию получите ошибку403
, проверьте:Включено ли использование GitHub Actions для пакетов в настройках организации.
Нужен ли вам персональный токен (PAT) с правами
write:packages
.
-
Build and push Docker image
context: .
— собираем образ из текущей директории.push: true
— отправляем образ в реестр.cache-from
/cache-to
— включаем кеш, чтобы повторные сборки были быстрее. (Если вы запускаете Workflow впервые, кеша ещё не будет — это нормально).-
tags
— публикуем образ сразу с двумя тегами:latest
— всегда указывает на последнюю версию;${{ github.sha }}
— уникальный тег по коммиту (нужен, чтобы откатиться к точной версии).
Откуда взять адрес?
После выполнения Workflow образ публикуется в GHCR с тегами latest
и уникальным хэшем коммита (${{ github.sha }}
).
Чтобы использовать этот образ в docker-compose.yml
, нужно указать полный адрес вместе с тегом.
Шаблон для GHCR:
ghcr.io/<организация_или_имя_пользователя>/<название_образа>:<тег>
Как составить адрес (даже если образ ещё не опубликован):
ghcr.io/
— домен GitHub Container Registry.<имя_пользователя>
или<организация>
— ваш логин GitHub или название организации (строго в нижнем регистре).<название_образа>
— обычно совпадает с именем репозитория (тоже в нижнем регистре).:тег
— версия образа. Это может бытьlatest
,1.0.0
, короткий SHA коммита или${{ github.sha }}
.
⚠️ Важно: имя образа (всё до двоеточия с тегом) должно быть в нижнем регистре, иначе Docker выдаст ошибку
invalid reference format
.
Для репозитория https://github.com/proDreams/tempProject корректный префикс будет:
ghcr.io/prodreams/tempproject
Примеры полных адресов:
ghcr.io/prodreams/tempproject:latest
ghcr.io/prodreams/tempproject:${{ github.sha }}
Отправляем изменения в GitHub и запускаем Workflow
Файл Workflow готов — пора отправить его в репозиторий.
Когда коммит попадёт на GitHub и сработает триггер (у нас — пуш в ветку main
), Actions автоматически запустит сборку.
Выполняем команды в терминале:
git add .github/workflows/build_and_deploy.yaml
git commit -m "CI/CD: Build docker image"
git push
После этого заходим на страницу репозитория и открываем вкладку Actions. Там отобразится выполняющийся Workflow:

Кликаем по запуску, чтобы посмотреть список заданий (jobs). В нашем случае оно одно и на него можно кликнуть:

Откроется подробный лог выполнения. Здесь можно увидеть каждый шаг и, если что-то пойдёт не так, понять причину:

У нас всё прошло успешно — образ отправился в реестр.
Теперь на главной странице репозитория в правом блоке Packages появится наш Docker-образ:

Если перейти по пакету, откроется страница с подробной информацией об образе:

Если что-то не сработало
-
Workflow не стартовал
проверьте, что пушите именно в ветку
main
(или измените ветку в триггере),убедитесь, что файл лежит в
.github/workflows/...
,проверьте, включены ли Actions в настройках репозитория.
-
Ошибка при пуше в GHCR
убедитесь, что в Workflow указано
permissions: packages: write
,при работе с организацией проверьте, что публикация пакетов из Actions разрешена,
если пакет приватный — настройте права доступа.
После успешного выполнения у вас есть адрес образа в GHCR и два тега:
latest
— всегда на последнюю сборку;${{ github.sha }}
— конкретная версия для стабильного деплоя.
Получаем и активируем Portainer Business Edition
Может возникнуть вопрос: зачем переходить на Business Edition, если до этого хватало версии Community?
Дело в том, что CE (Community Edition) покрывает базовые потребности: можно управлять Docker-хостами, контейнерами и стаками.
Однако, например, создание Webhook’ов для автоматического обновления стаков и контейнеров там недоступно — это уже функционал Business Edition.
Хорошая новость: Business Edition можно получить бесплатно, но с одним ограничением — не более трёх Docker-хостов (включая локальный).
Для домашних или небольших проектов этого обычно достаточно.
Как получить ключ
Переходим на страницу программы Take 3:
? https://www.portainer.io/take-3

Справа находится форма. Заполняем её:
Имя и фамилия
Электронная почта — укажите рабочую, именно туда придёт ключ.
Номер телефона — российский номер проходит без проблем.
Страна — выбираем «Россия» (есть в списке).
Использовали ли вы Portainer CE? — отвечаем «Да».
Используемая платформа контейнеризации — в моём случае «Docker Standalone».
Как вы используете Portainer? — я указал «Для дома».
Как узнали о Portainer — выбирайте любой подходящий вариант.
И не забудьте поставить галочку согласия с лицензионным соглашением.
После нажатия «Submit» ждём письмо на почту с кодом активации.
Обновление Portainer CE до BE
Обновить Portainer до Business Edition очень просто.
Если вы используете DockerHosting.ru, то предустановленный Portainer доступен по адресу:
https://<ip_сервера>:9000/
В верхнем левом углу, над логотипом, есть кнопка «Upgrade to Business Edition». Нажимаем её — откроется окно для ввода ключа:

Вводим полученный ключ и нажимаем «Start upgrade».
После этого начнётся процесс обновления. Когда он завершится, система попросит вас снова войти в аккаунт:

⚡ Важно: при обновлении до BE ваши контейнеры, стеки и настройки не пропадут.
Portainer просто активирует дополнительные функции, в том числе возможность использовать Webhook’и.
Подключение реестра в Portainer
Теперь подключим к Portainer внешний реестр GitHub. Это нужно, чтобы при деплое Portainer мог сам авторизоваться в реестре и скачать образ.
В левом меню выберите Registries — откроется список подключённых реестров:

Нажмите Add Registry. Вы попадёте на страницу добавления:

Из вариантов выбираем GitHub (для GitHub Container Registry):

Теперь заполняем поля:
Name — любое название, например
github
.Username — ваш логин GitHub.
Personal Access Token — ваш персональный токен GitHub. Создать его можно здесь: ? https://github.com/settings/tokens
Для скачивания образов достаточно права read:packages.
Не давайте лишних разрешений: Portainer будет только читать образы, а пушит их ваш CI.
После сохранения вы вернётесь к списку реестров, где появится подключение к GitHub:

Примечания
Если образы находятся в приватной организации, убедитесь, что токен имеет доступ к этой организации и к пакетам.
Для Portainer используйте минимально достаточные права (обычно read-only).
⚠️ Не путайте Personal Access Token с
GITHUB_TOKEN
, который используется в GitHub Actions.GITHUB_TOKEN
работает только внутри CI, а для Portainer нужен отдельный PAT.
Добавление GitHub Container Registry в Portainer CE
В Portainer Community Edition нет отдельного готового варианта для GitHub, но подключить реестр всё равно можно без проблем.
Для этого при добавлении выбираем Custom registry:

Далее заполняем поля:
Name — любое удобное название, например
GitHub
.Registry URL — указываем
ghcr.io
.-
Включаем Authentication и вводим:
Username — ваш логин GitHub,
Password/Token — персональный токен (PAT). Создать его можно здесь: ? https://github.com/settings/tokens.
Для скачивания образов достаточно права read:packages.
Помните: этот токен отличается отGITHUB_TOKEN
, который используется внутри GitHub Actions. Здесь нужен именно ваш PAT.
После нажатия Add registry реестр появится в списке и будет готов к использованию.
Создание стека в Portainer
В первой статье мы уже пробовали создавать стек из Git-репозитория.
Теперь сделаем то же самое, но используем только файл docker-compose.yml
— немного изменив его.
Изменение docker-compose.yml
Исходный файл выглядел так:
services:
test-bot:
build: .
container_name: test-bot
environment:
- BOT_TOKEN=${BOT_TOKEN}
Здесь сервис собирает образ локально (build: .
).
Но теперь у нас уже есть готовый образ в реестре, поэтому локальная сборка не нужна.
Чтобы подтянуть образ из реестра, заменяем строку build: .
на image:
и указываем полный адрес с тегом:
services:
test-bot:
image: ghcr.io/prodreams/tempproject:latest
container_name: test-bot
environment:
- BOT_TOKEN=${BOT_TOKEN}
Стек в Portainer
Возвращаемся в Portainer и переходим в раздел Stacks:

Нажимаем Add stack.
Откроется страница создания стека. Выбираем вариант Web editor:

Вверху указываем название стека. В моём случае это test-bot
.
Ниже в поле редактора вставляем содержимое нашего docker-compose.yml
:

Теперь прокручиваем страницу вниз и включаем переключатель Create a Stack webhook.
Это создаст специальную ссылку для вебхука — в будущем мы сможем отправлять на неё запросы из CI/CD, чтобы автоматически обновлять проект.
Также в разделе Environment variables добавляем переменную окружения BOT_TOKEN
— сюда вписывается токен нашего Telegram-бота.
После этого жмём Deploy the stack:

Через несколько секунд нас перенаправит на страницу запущенных стаков:

Осталось проверить, что бот работает:

⚡ Если бот не запускается, загляните в логи контейнера — скорее всего, ошибка в переменных окружения или токен введён неправильно.
Получение адреса вебхука
Для следующего шага — настройки Workflow деплоя — нам понадобится адрес Webhook для созданного стека.
Находясь на странице Stacks, выбираем наш стек и переходим на страницу его управления:

В верхней части нажимаем Editor, чтобы открыть режим редактирования.
В блоке Webhooks будет показана ссылка вебхука.
Нажимаем Copy link, чтобы скопировать её:

Эта ссылка пригодится нам в CI/CD Workflow: мы будем отправлять на неё запрос, чтобы автоматически обновлять проект при новых сборках.
Workflow деплоя
Переходим к самому интересному — деплою проекта через Webhook Portainer в CI/CD.
Добавляем секрет в GitHub Actions
Прежде чем писать Workflow, нужно сохранить ссылку вебхука в секрете GitHub.
Это важно по двум причинам:
ссылка не попадёт в открытый доступ;
её значение не будет видно в логах Workflow.
Открываем репозиторий на GitHub и заходим в раздел Settings:

Слева выбираем Secrets and variables → Actions. Откроется страница управления секретами:

Нажимаем зелёную кнопку New repository secret.
В появившейся форме:
в поле Name указываем название секрета —
PORTAINER_WEBHOOK_URL
.в поле Secret вставляем скопированную ранее ссылку вебхука.

Нажимаем Add secret — теперь этот секрет можно использовать в Workflow:

⚡ Совет: секреты никогда не хранят в коде Workflow напрямую.
Если вы случайно закоммитите ссылку вебхука в репозиторий, любой сможет деплоить ваш проект.
Дополняем build_and_deploy.yaml
Откроем файл build_and_deploy.yaml
и добавим новую задачу (job), которая будет выполняться после сборки образа.
Она вызовет вебхук, чтобы Portainer автоматически подтянул новый образ и перезапустил проект.
На уровне с build-and-push
создаём новый блок deploy
:
deploy:
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Trigger Portainer webhook
env:
PORTAINER_WEBHOOK_URL: ${{ secrets.PORTAINER_WEBHOOK_URL }}
run: curl -fsS -m 30 -X POST "$PORTAINER_WEBHOOK_URL"
Что здесь происходит
needs: build-and-push
Эта строчка говорит GitHub Actions: «Запусти этот job только после успешного выполненияbuild-and-push
». То есть деплой произойдёт только если образ собрался и загрузился в реестр.env
В переменную окруженияPORTAINER_WEBHOOK_URL
передаём значение секрета из GitHub (${{ secrets.PORTAINER_WEBHOOK_URL }}
). Так мы не храним ссылку вебхука в открытом виде.-
curl -fsS -m 30 -X POST "$PORTAINER_WEBHOOK_URL"
Эта команда отправляет HTTP POST-запрос на вебхук Portainer. Параметры:-f
— завершить команду ошибкой, если статус-код ответа ≥ 400;-sS
— тихий режим без лишних логов, но с выводом ошибок;-m 30
— ограничение времени выполнения 30 сек.;-X POST
— используем POST-запрос;"$PORTAINER_WEBHOOK_URL"
— сама ссылка вебхука.
Всего одна команда — и Portainer перезапускает стек с новым образом.
Полный код файла:
name: Build and Deploy Project
on:
push:
branches:
- main
permissions:
packages: write
contents: read
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
cache-from: type=registry,ref=ghcr.io/prodreams/tempproject:latest
cache-to: type=inline
tags: |
ghcr.io/prodreams/tempproject:latest
ghcr.io/prodreams/tempproject:${{ github.sha }}
deploy:
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Trigger Portainer webhook
env:
PORTAINER_WEBHOOK_URL: ${{ secrets.PORTAINER_WEBHOOK_URL }}
run: curl -fsS -m 30 -X POST "$PORTAINER_WEBHOOK_URL"
Дополняем код бота
Ранее мы уже запустили бота, создав стек в Portainer.
Чтобы проверить обновление проекта через новый деплой, немного расширим функционал бота и добавим тестовую команду.
Обновлённый код:
import asyncio
import os
from aiogram import Dispatcher, Bot
from aiogram.filters import Command
from aiogram.types import Message
from dotenv import load_dotenv
load_dotenv()
async def send_message(message: Message) -> None:
await message.answer(text="Hello World!")
async def send_sticker(message: Message) -> None:
await message.answer_sticker(sticker="CAACAgIAAxkBAAEKbW1lGVW1I6zFVLyovwo2rSgIt1l35QADJQACYp0ISWYMy8-mubjIMAQ")
async def start():
bot = Bot(token=os.getenv('BOT_TOKEN'))
dp = Dispatcher()
dp.message.register(send_sticker, Command(commands="test"))
dp.message.register(send_message)
try:
await dp.start_polling(bot)
finally:
await bot.session.close()
if __name__ "__main__":
asyncio.run(start())
Что изменилось:
Добавлена функция
send_sticker
, которая отправляет стикер.-
В диспетчер зарегистрирован обработчик:
команда
/test
вызываетsend_sticker
,любое другое сообщение вызывает
send_message
и отвечает «Hello World!».
Теперь у нас есть удобный способ проверить, что новая версия бота действительно задеплоилась: достаточно отправить /test
и убедиться, что в ответ приходит стикер.
Пуш изменений
Осталось последний шаг — отправить изменения в удалённый репозиторий.
После этого GitHub Actions сразу запустит сборку новой версии образа и деплой через Portainer.
Выполняем знакомые команды:
git add .github/workflows/build_and_deploy.yaml docker-compose.yaml main.py
git commit -m "CI/CD: Deploy"
git push
Теперь заходим в репозиторий на GitHub и открываем раздел Actions:

Видим, что запустился Workflow, и теперь в нём выполняются две задачи:

Если перейти в подробности задачи deploy, можно убедиться, что всё прошло успешно.
Обратите внимание: адрес вебхука в логах не отображается — это сделано для безопасности, так как он хранится в GitHub Secrets:

Теперь проверим бота:

Бот работает корректно!
CI/CD-процесс полностью автоматизирован:
при каждом пуше в
main
собирается новый образ,Portainer подтягивает его и перезапускает стек,
а вы сразу можете протестировать новую версию.
Заключение
За эти три статьи мы шаг за шагом разобрались, как устроен процесс деплоя:
от запуска приложения напрямую из Git-репозитория — до полноценного автоматизированного CI/CD.
В чём преимущество Portainer с Webhook
Обычно деплой выглядит так:
Workflow подключается к серверу по SSH и выполняет там команды для обновления проекта.
Этот способ подходит для сложных систем, но для большинства небольших проектов он слишком громоздкий и неудобный.
С Portainer всё проще:
через удобный интерфейс вы создаёте контейнеры и управляете ими,
а благодаря Webhook контейнеры автоматически обновляются после каждой сборки нового образа.
Такой подход особенно ценен для новичков: он позволяет быстро и безопасно внедрить CI/CD без лишней сложности.
Подписывайтесь на наш Telegram‑канал «Код на салфетке» —
там вы найдёте ещё больше полезных материалов, как для новичков, так и для опытных разработчиков!
Gorushka
Начинаю следить за статьями автора, а тут вроде все грамотно (читал по диагонали одним глазом) ))))
proDream Автор
Стараюсь, но если где ошибаюсь, смело меня поправляйте)