---
title: "Блог на Hugo в K3s: часть 1 - архитектура и первый запуск"
date: 2026-01-03
draft: false
description: "Переезд с Jekyll на Hugo: выбор темы Blowfish, настройка K3s кластера и два окружения с нуля. Старт цикла о production блоге на Open Source стеке."
tags: ["hugo", "blowfish", "gitea", "homelab", "devops"]
categories: ["Мои проекты"]
series: ["Блог на Hugo в K3s"]
series_order: 1
---

Предыдущий блог жил на 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 проект

```bash
# Создаём новый 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

```toml
baseURL = "https://blog.ru/"
theme = "blowfish"
defaultContentLanguage = "ru"
```

### languages.ru.toml

```bash
# Переименовываем файл языка для русского
mv config/_default/languages.en.toml config/_default/languages.ru.toml
```

```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

```bash
# Переименовываем файл меню
mv config/_default/menus.en.toml config/_default/menus.ru.toml
```

```toml
[[main]]
  name = "Блог"
  pageRef = "posts"
  weight = 10

[[main]]
  name = "О сайте"
  pageRef = "about"
  weight = 20
```

---

## Шаг 3: Первая статья

```bash
# Создаём новую статью
hugo new content posts/hello-world/index.md
```

```markdown
---
title: "Hello World"
date: 2026-02-13
draft: false
description: "Первый пост на новом сайте"
tags: ["test"]
categories: ["Веб-разработка"]
---

Это первый пост на blog.ru.
```

---

## Шаг 4: Проверяем локально

```bash
# Запускаем локальный сервер 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

```bash
# Добавляем все файлы в 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):**

```bash
# Переходим в папку проекта
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) - вижу статью как её увидят читатели.

**Публикую:**

```bash
# Переключаемся на 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
