MAATRIX / Блог / Бэкап и восстановление Woodpecker CI

Бэкап и восстановление Woodpecker CI

MAATRIX

Woodpecker CI выглядит обманчиво просто: server, agent, пара переменных окружения — и кажется, что бэкапить особо нечего, ведь весь код лежит в git-репозиториях. На деле в базе Woodpecker копится то, что руками не восстановишь: секреты репозиториев, OAuth-привязка к git-провайдеру, права пользователей и вся история пайплайнов. Разберём, что там реально хранится, как снять рабочий бэкап для SQLite и Postgres-инсталляций и как поднять Woodpecker на новом сервере, не переживая заново авторизацию каждого репозитория.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Что теряете, если Woodpecker не бэкапить

Woodpecker — форк Drone CI, который сообщество продолжило как полностью открытый проект после того, как Drone ушёл под коммерческую лицензию (подробно об этом переходе и установке — в статье про установку Woodpecker CI на VPS). Архитектурно server — это тонкий слой поверх базы данных: сам он не хранит исходный код и не собирает артефакты на постоянной основе, но именно в базе живёт всё, что нельзя восстановить простым git clone.

При потере базы без бэкапа вы теряете разом:

  • список включённых репозиториев и их привязку к OAuth-приложению git-провайдера;
  • секреты (Repository → Settings → Secrets) — токены деплоя, пароли registry, SSH-ключи для сборок;
  • пользователей и права — кто админ, кто может запускать пайплайны, кто только смотрит;
  • историю и логи сборок — не критично для работы CI, но полезно для разбора инцидентов;
  • cron-задачи и per-repo настройки (таймауты, ограничения параллелизма).

Сам код и .woodpecker.yml не пострадают — они лежат в репозитории на Gitea/GitHub/GitLab. Но заново подключать 20-30 репозиториев, вбивать секреты и раздавать права — это часы ручной работы, которых бэкап на 5 минут в день полностью избегает.

Что физически хранит Woodpecker и где это искать

Server держит состояние в одном месте — базе данных. Драйвер задаётся переменной WOODPECKER_DATABASE_DRIVER:

ДрайверГде хранитсяКогда используется
sqlite3 (по умолчанию)файл /var/lib/woodpecker/woodpecker.sqlite внутри контейнератест, один-два разработчика, невысокая нагрузка
postgresвнешний Postgres-сервербоевая нагрузка, несколько параллельных пайплайнов
mysqlвнешний MySQL/MariaDB-серверреже, но поддерживается

Второй важный кусок — не в базе, а в файлах развёртывания: docker-compose.yml и .env с WOODPECKER_AGENT_SECRET (общий секрет между server и agent) и клиентскими данными OAuth-приложения (WOODPECKER_GITEA_CLIENT / WOODPECKER_GITEA_SECRET или аналоги для GitHub/GitLab). Без этих значений после восстановления базы agent не подключится к серверу, а авторизация через git-провайдер не заработает, даже если сама база цела.

Что бэкапить НЕ нужно: сам код репозиториев (он в Gitea/GitHub), .woodpecker.yml пайплайнов (тоже в репозитории), рабочие директории сборок внутри контейнеров agent — они пересоздаются с нуля на каждый запуск и не персистентны в принципе.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Бэкап SQLite-инсталляции

Для дефолтной установки (Docker Compose, том woodpecker-server-data) файл базы лежит внутри volume. Снимать копию проще всего через короткую остановку server — при типичной нагрузке в одного-двух разработчиков простой в 5-10 секунд не критичен:

mkdir -p /backup/woodpecker
cd /opt/woodpecker

docker compose stop woodpecker-server

docker run --rm \
  -v woodpecker_woodpecker-server-data:/data \
  -v /backup/woodpecker:/backup \
  alpine sh -c "apk add --no-cache sqlite >/dev/null && \
    sqlite3 /data/woodpecker.sqlite '.backup /backup/woodpecker-$(date +%F).sqlite'"

docker compose start woodpecker-server

Имя тома woodpecker_woodpecker-server-data собирается Docker Compose из имени проекта (по умолчанию — имя папки, /opt/woodpeckerwoodpecker) и имени тома из docker-compose.yml. Если у вас другое имя папки проекта, уточните реальное имя тома командой:

docker volume ls | grep woodpecker

.backup — штатная команда SQLite для консистентного снимка через backup API, а не просто копирование файла: она безопасна даже при активной записи, но короткая остановка server всё равно снижает риск поймать базу в момент долгой транзакции до нуля. Копию файла woodpecker.sqlite без .backup (обычным cp) делать не стоит — на живой базе можно получить повреждённый файл, если в момент копирования шла запись.

Рядом сохраните файлы развёртывания — без них база бесполезна:

cp /opt/woodpecker/docker-compose.yml /opt/woodpecker/.env /backup/woodpecker/

Бэкап Postgres-инсталляции

На боевой нагрузке база обычно вынесена в отдельный сервис Postgres — либо в том же compose-стеке, либо на отдельном сервере. Бэкап делается штатным pg_dump, без остановки Woodpecker — Postgres сам обеспечивает консистентность снимка на лету:

docker compose exec -T postgres pg_dump -U woodpecker -d woodpecker \
  > /backup/woodpecker/woodpecker-db-$(date +%F).sql

Если Postgres стоит вне Docker, на отдельном сервере:

pg_dump -h db.example.com -U woodpecker -d woodpecker \
  -f /backup/woodpecker/woodpecker-db-$(date +%F).sql

Для MySQL/MariaDB — тот же принцип, другой инструмент:

docker compose exec -T mysql mysqldump -u woodpecker -p'PASSWORD' woodpecker \
  > /backup/woodpecker/woodpecker-db-$(date +%F).sql

Сжимать дамп имеет смысл сразу — текстовый SQL-дамп базы Woodpecker с историей сборок сжимается в разы:

gzip /backup/woodpecker/woodpecker-db-$(date +%F).sql

Как и в случае с SQLite, docker-compose.yml и .env бэкапьте отдельным шагом — без WOODPECKER_AGENT_SECRET и данных OAuth-приложения восстановленный сервер не заработает даже с полностью целой базой.

Секреты в базе: что важно понимать перед восстановлением

Секреты репозиториев (Repository → Settings → Secrets) и общесистемные секреты организации хранятся прямо в базе данных Woodpecker вместе с остальными настройками — отдельного файла-ключа, как, например, secrets/master.key у Jenkins, у Woodpecker нет. Это удобно с точки зрения бэкапа: один дамп базы уносит с собой и конфигурацию репозиториев, и секреты — не нужно синхронизировать два независимых источника.

Обратная сторона — к бэкапу базы нужно относиться как к чувствительному файлу: он содержит токены деплоя и пароли от registry. Права на директорию с бэкапами стоит ограничить сразу:

chmod 700 /backup/woodpecker
chown root:root /backup/woodpecker

Если бэкапы уходят во внешнее хранилище, шифруйте их на этапе заливки — подход из статьи про бэкап с шифрованием на VPS применим к любому дампу, не только к файловым архивам.

Автоматизация и офсайт-копия

Локальный бэкап на том же диске, где крутится Woodpecker, не спасает при отказе диска целиком или компрометации сервера. Минимальная рабочая схема — cron-скрипт, который снимает дамп и сразу уносит его на другую машину.

Пример для SQLite-инсталляции с заливкой через rclone в S3-совместимое хранилище:

#!/bin/bash
# /usr/local/bin/woodpecker-backup.sh
set -euo pipefail

BACKUP_DIR=/backup/woodpecker
DATE=$(date +%F)
VOLUME=woodpecker_woodpecker-server-data

mkdir -p "$BACKUP_DIR"

docker compose -f /opt/woodpecker/docker-compose.yml stop woodpecker-server

docker run --rm \
  -v "${VOLUME}:/data" \
  -v "${BACKUP_DIR}:/backup" \
  alpine sh -c "apk add --no-cache sqlite >/dev/null && \
    sqlite3 /data/woodpecker.sqlite '.backup /backup/woodpecker-${DATE}.sqlite'"

docker compose -f /opt/woodpecker/docker-compose.yml start woodpecker-server

cp /opt/woodpecker/docker-compose.yml /opt/woodpecker/.env "${BACKUP_DIR}/"
tar czf "${BACKUP_DIR}/woodpecker-config-${DATE}.tar.gz" \
  -C "${BACKUP_DIR}" docker-compose.yml .env

rclone copy "${BACKUP_DIR}/woodpecker-${DATE}.sqlite" remote:woodpecker-backups/
rclone copy "${BACKUP_DIR}/woodpecker-config-${DATE}.tar.gz" remote:woodpecker-backups/

find "${BACKUP_DIR}" -name 'woodpecker-*' -mtime +14 -delete
0 3 * * * /usr/local/bin/woodpecker-backup.sh >> /var/log/woodpecker-backup.log 2>&1

Если под S3-хранилище удобнее держать свой сервер, а не облако третьей стороны — подойдёт MinIO на отдельном VPS, бэкап и восстановление которого разобраны в статье про бэкап и восстановление MinIO. Для Postgres-версии тот же скрипт достаточно поменять на pg_dump вместо блока с sqlite3 — остальная часть с заливкой и ротацией не меняется.

Добавьте проверку, что скрипт реально отработал, а не упал молча — например, пинг в healthchecks.io последней строкой скрипта (curl -fsS https://hc-ping.com/<uuid> > /dev/null). Бэкап, о падении которого никто не узнал месяц, эквивалентен отсутствию бэкапа.

Восстановление на новом сервере

Порядок действий одинаковый для обоих типов базы, отличается только шаг с самой базой.

  1. Поднять чистый Woodpecker той же версии на новом VPS — Docker, docker-compose.yml, .env из бэкапа:
mkdir -p /opt/woodpecker && cd /opt/woodpecker
cp /backup/woodpecker/docker-compose.yml /backup/woodpecker/.env .
  1. Для SQLite — вернуть файл базы в volume до первого запуска server:
docker volume create woodpecker_woodpecker-server-data

docker run --rm \
  -v woodpecker_woodpecker-server-data:/data \
  -v /backup/woodpecker:/backup \
  alpine cp /backup/woodpecker-2026-08-24.sqlite /data/woodpecker.sqlite

Для Postgres — сначала поднять пустую базу, затем накатить дамп:

docker compose up -d postgres
sleep 5
gunzip -c /backup/woodpecker/woodpecker-db-2026-08-24.sql.gz | \
  docker compose exec -T postgres psql -U woodpecker -d woodpecker
  1. Запустить весь стек и проверить логи:
docker compose up -d
docker compose logs -f woodpecker-server
  1. Зайти в веб-интерфейс на новом домене, убедиться, что репозитории на месте, секреты видны (сами значения UI не покажет — только факт наличия), а agent подключился (статус в интерфейсе или в логах docker compose logs woodpecker-agent).

Если домен сменился, обновите WOODPECKER_HOST в .env и поправьте Redirect URI в OAuth-приложении git-провайдера — иначе авторизация и вебхуки молча не заработают, хотя база восстановлена корректно. Если новый VPS выбирали заново, ориентир по CPU/RAM для связки CI/CD есть в статье про выбор VPS для разработчика и CI/CD.

WOODPECKER_AGENT_SECRET менять не обязательно — можно оставить прежний из бэкапа .env или сгенерировать новый, главное, чтобы значение у server и agent совпадало. Ключи OAuth-приложения менять не нужно вовсе, если само приложение в Gitea/GitHub не пересоздавалось.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Нужно ли отдельно бэкапить сами репозитории с кодом?

Нет, код живёт в Gitea/GitHub/GitLab, а не в Woodpecker — бэкап git-сервера ведётся отдельно и по своему расписанию.

Что теряется, если восстановить только конфиг без базы?

Всё содержимое: список репозиториев, секреты, пользователи, история сборок. .env и docker-compose.yml без базы поднимут пустой, «свежий» Woodpecker, где всё придётся настраивать заново.

Можно ли бэкапить SQLite-файл без остановки server?

Через sqlite3 .backup — можно, backup API SQLite безопасен при конкурентной работе. Обычным cp файла на живой базе так делать не стоит: есть шанс поймать файл в промежуточном состоянии при активной записи.

Как часто нужен бэкап для CI-сервера?

Раз в сутки обычно достаточно — Woodpecker не хранит данные, которые критично терять за меньший интервал (в отличие, например, от базы с заказами). Если секреты и репозитории меняются часто, имеет смысл сократить интервал до нескольких часов.

История сборок восстановится один в один?

Да, если бэкапили базу целиком — логи и статусы прошлых пайплайнов хранятся там же и восстанавливаются вместе со всем остальным без отдельных шагов.

Что делать, если старый сервер ещё жив, а новый уже поднят?

Держите оба запущенными до полной проверки нового — переключайте DNS/reverse-proxy на новый адрес только после того, как agent на новом сервере подхватил задание и хотя бы один пайплайн прошёл успешно.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →