11 KiB
| title | date | draft | description | tags | categories | series | series_order | showComments | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Блог на Hugo в K3s: часть 1 - архитектура и первый запуск | 2026-01-03 | false | Как я переехал с Jekyll на Hugo, почему выбрал тему Blowfish и как настроил два окружения с нуля. Начало цикла о том как я строил oakazanin.ru. |
|
|
|
1 | true |
Предыдущий блог жил на Jekyll. Жил - громко сказано. Скорее существовал, периодически ломаясь при обновлениях и требуя ритуальных танцев с Ruby каждый раз когда я садился за новую статью.
Однажды я решил что хватит.
Почему не Jekyll
Jekyll - зрелый инструмент с большим сообществом. Но у него есть фундаментальная проблема: он написан на Ruby.
Звучит невинно. На практике это означает:
Версионный ад. Ruby, Bundler, Gems - у каждого своя версия, и они регулярно конфликтуют друг с другом. Клонируешь репозиторий на новую машину - час уходит на то чтобы собрать рабочее окружение. Обновляешь Jekyll - ломаются плагины. Обновляешь плагины - ломается что-то ещё.
Зависимости ради зависимостей. Простой блог тянет 1000+ gems, половина из которых устаревшая или находится в состоянии "поддерживается постольку-поскольку". Каждый bundle install - это лотерея.
Лишний мусор. Jekyll генерирует страницы без ссылок, которые непонятно зачем существуют. Приходится явно прописывать что не генерировать.
Hugo решает все эти проблемы радикально: это один бинарник на Go. Никаких зависимостей, никаких конфликтов версий, никакого bundle install. Скачал - работает. На любой машине, всегда.
Бинарник весит ~50MB. Собирает сайт из 40+ страниц за полторы секунды. Jekyll на том же контенте думал заметно дольше.
Почему Blowfish
Все просто: понравилась.
Смотрел темы несколько часов. Blowfish выглядела именно так как я хотел - чисто, без лишнего, с хорошей типографикой. Взял её.
Ставится как Git submodule - обновляется одной командой, не засоряет репозиторий.
Архитектура: два окружения
У любого нормального инфраструктурного проекта есть тестовый и production контур. Блог - не исключение: обновления Hugo, изменения темы, новые статьи - всё это нужно проверять до того как это увидят читатели.
┌────────────────────────────────────┐
│ Gitea (внутренний) │
│ репозиторий: blog │
│ │
│ ветка: main ветка: dev │
└───────┬────────────────────┬───────┘
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ hugo-builder │ │ hugo-builder │
│ prod │ │ dev │
└───────┬───────┘ └───────┬───────┘
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ nginx │ │ nginx-dev │
│ blog.ru │ │ dev.blog.ru │
│ (публичный) │ │ (Basic Auth) │
└───────────────┘ └───────────────┘
Production (blog.ru) - публичный. Ветка main.
Development (dev.blog.ru) - тестовый контур. Ветка dev. Закрыт Basic Auth через Traefik Middleware - без логина и пароля не войти.
Оба окружения в одном K3s namespace. Один репозиторий, две ветки, два независимых пайплайна.
Шаг 1: Создаём Hugo проект
# Создаём новый Hugo сайт
cd ~/projects
hugo new site blog
cd blog
# Инициализируем Git репозиторий
git init
git remote add origin https://git.example.com/user/blog.git
# Добавляем тему Blowfish как Git submodule
git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish
# Подтягиваем файлы submodule
git submodule update --init --recursive
# Удаляем дефолтный конфиг Hugo
rm -f hugo.toml
# Создаём структуру для конфигов
mkdir -p config/_default
# Копируем примеры конфигов из темы
cp themes/blowfish/config/_default/*.toml config/_default/
Структура после инициализации:
blog/
├── content/ ← статьи в Markdown
├── static/ ← изображения, favicon
├── themes/
│ └── blowfish/ ← тема (Git submodule)
└── config/
└── _default/
├── hugo.toml
├── languages.ru.toml
├── menus.ru.toml
├── markup.toml
└── params.toml
Шаг 2: Минимальная конфигурация
hugo.toml
baseURL = "https://blog.ru/"
theme = "blowfish"
defaultContentLanguage = "ru"
languages.ru.toml
# Переименовываем файл языка для русского
mv config/_default/languages.en.toml config/_default/languages.ru.toml
languageCode = "ru"
languageName = "Русский"
weight = 1
title = "Мой Блог"
[params]
displayName = "RU"
isoCode = "ru"
rtl = false
dateFormat = "2 January 2006"
[params.author]
name = "Ваше Имя"
headline = "DevOps Engineer | Kubernetes | Homelab"
menus.ru.toml
# Переименовываем файл меню
mv config/_default/menus.en.toml config/_default/menus.ru.toml
[[main]]
name = "Блог"
pageRef = "posts"
weight = 10
[[main]]
name = "О сайте"
pageRef = "about"
weight = 20
Шаг 3: Первая статья
# Создаём новую статью
hugo new content posts/hello-world/index.md
---
title: "Hello World"
date: 2026-02-13
draft: false
description: "Первый пост на новом сайте"
tags: ["test"]
categories: ["Веб-разработка"]
---
Это первый пост на blog.ru.
Шаг 4: Проверяем локально
# Запускаем локальный сервер Hugo
# -D: показывать черновики (draft: true)
# --bind 0.0.0.0: доступен с любого IP в локальной сети
hugo server -D --bind 0.0.0.0
Открываем http://192.168.11.10:1313/ (ваш IP) - сайт с Blowfish темой и первой статьёй.
Hugo автоматически пересобирает сайт при сохранении файлов - изменения видны сразу после обновления страницы в браузере.
Шаг 5: Пушим в Gitea
# Добавляем все файлы в Git
git add .
# Создаём первый коммит
git commit -m "Initial commit: Hugo + Blowfish v2.98.0"
# Переименовываем ветку в main (если по умолчанию master)
git branch -M main
# Пушим в Gitea
git push -u origin main
# Создаём ветку dev для development окружения
git checkout -b dev
git push -u origin dev
# Возвращаемся на main
git checkout main
Workflow: как я пишу статьи
Пишу (ветка dev):
# Переходим в папку проекта
cd ~/projects/blog
# Переключаемся на ветку dev
git checkout dev
# Запускаем локальный предпросмотр
hugo server -D --bind 0.0.0.0
# Создаём новую статью
hugo new content posts/название-статьи/index.md
# Редактируем в любом редакторе, сохраняем, смотрим в браузере
# Когда готово - коммитим
git add .
git commit -m "feat: новая статья про X"
# Пушим в dev ветку
git push origin dev
# → webhook срабатывает → автосборка → dev.blog.ru
Проверяю:
Открываю https://dev.blog.ru (вводя логин/пароль Basic Auth) - вижу статью как её увидят читатели.
Публикую:
# Переключаемся на main
git checkout main
# Мержим изменения из dev
git merge dev
# Пушим в production
git push origin main
# → webhook срабатывает → автосборка → blog.ru
Золотое правило: все изменения только через ветку dev. В main - только через merge. Никогда не редактировать файлы находясь на main.
Почему это важно - расскажу отдельно, когда дойдём до того как я нарушил это правило и что из этого вышло.
Что дальше
Локально всё работает. Но hugo server умрёт как только закрою терминал.
Нужно развернуть это в K3s: Hugo Builder который пересобирает сайт при каждом пуше, Nginx который раздаёт статику, SSL сертификаты, NFS хранилище. Об этом - в следующей части.
Стек этой части:
- Hugo v0.155.3 extended
- Blowfish v2.98.0
- Gitea (внутренний)
- Рабочая станция: Debian/Ubuntu