Долгое время мой блог жил на VDS — nginx, certbot, systemd-сервисы, бекапы, короче полноценный сервер. В какой-то момент стало лень следить за ещё одной машиной и особенно меня подтолкнули проблемы с разными хостингами и я решил переехать на статический S3-хостинг.

Пост про то — как я переехал, с какими граблями столкнулся и что в итоге получилось.

Почему Timeweb S3 Link to heading

Почему именно Timeweb, а не Яндекс Облако или Selectel? Тут всё просто: у меня уже были там аккаунты, а S3 у них стоит копейки. Для статики — идеально.

Из коробки Timeweb S3 умеет раздавать статический сайт, выдаёт URL вида *.website.twcstorage.ru, поддерживает кастомные заголовки и политики.

Что нужно Link to heading

  • Аккаунт в Timeweb (уже был)
  • Hugo
  • Репозиторий блога на GitHub (уже был)
  • Домен (уже был)

Шаг 1: Создание бакета Link to heading

  1. Заходим в панель Timeweb → S3 → Создать бакет
  2. Выбираем регион, имя бакета
  3. После создания открываем бакет → Настройки → Хостинг статического сайта → Включаем
  4. Получаем URL: https://<id>.website.twcstorage.ru

Шаг 2: S3-ключи Link to heading

В панели Timeweb идём в сервисные аккаунты → создаём ключ с доступом к бакету (на запись и чтение). Нам понадобятся:

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_ENDPOINT_URLhttps://s3.twcstorage.ru
  • И имя бакета

Шаг 3: GitHub Actions Link to heading

Деплой настроил через GitHub Actions. Workflow выглядит так:

name: Deploy blog to S3

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: peaceiris/actions-hugo@v3
        with:
          extended: true

      - run: hugo --gc

      - uses: jakejarvis/s3-sync-action@master
        with:
          args: --delete
        env:
          AWS_S3_BUCKET: ${{ secrets.AWS_BUCKET_NAME }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_ENDPOINT_URL: ${{ secrets.AWS_ENDPOINT_URL }}

Секреты (AWS_*) храню в настройках репозитория GitHub — Settings → Secrets and variables → Actions.

Шаг 4: Деплой Link to heading

Пушим в main:

git push origin main

GitHub Actions собирает сайт и синхронизирует с бакетом. Всё, сайт на S3, никаких ssh и ручных заливок.

Шаг 5: Домены (не так всё просто) Link to heading

Настраиваем CNAME в DNS:

blog.tatarinovms.ru → s3.twcstorage.ru
blog.tatarinovms.space → s3.twcstorage.ru

Ждём, пока DNS обновится, открываем браузер… и получаем AccessDenied.

Подводный камень: кастомные домены не работают Link to heading

Как оказалось, Timeweb S3 не поддерживает кастомные домены. Вообще. Только их *.website.twcstorage.ru. На момент написания поста ответ поддержки это подтвердил.

Есть два пути:

Вариант 1: CloudFlare Меняем NS на CloudFlare, он проксирует запросы на S3-эндпоинт и выдаёт свой SSL. VDS не нужен. Бесплатно. Но мы знаем как в некоторых регионах работает CloudFlare, скажем мягко - с перебоями.

Вариант 2: nginx reverse proxy Пришла простая идея, так как есть в загашнике VDS, то мы ставим nginx на VDS, он проксирует кастомные домены на S3 URL. SSL через certbot. VDS всё равно есть под другие задачи. Конфиг nginx:

server {
    listen 80 default_server;
    server_name blog.tatarinovms.ru blog.tatarinovms.space;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name blog.tatarinovms.ru blog.tatarinovms.space;

    ssl_certificate /etc/letsencrypt/live/blog.tatarinovms.space/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.tatarinovms.space/privkey.pem;

    location / {
        proxy_pass https://id.website.twcstorage.ru;
        proxy_ssl_verify off;
        proxy_ssl_server_name on;
        proxy_set_header Host id.website.twcstorage.ru;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

SSL получил через certbot:

certbot --nginx -d blog.tatarinovms.ru -d blog.tatarinovms.space

Итоговая схема Link to heading

Пуш в main → GitHub Actions → Hugo build → aws s3 sync → S3 бакет → DNS: blog.tatarinovms.ru / blog.tatarinovms.space → A → VDS → nginx reverse proxy → S3 (*.website.twcstorage.ru)

При пуше в main GitHub Actions сам собирает Hugo и синхронизирует с S3 — nginx просто проксирует то, что уже лежит в бакете. Редеплой занимает меньше минуты.

Timeweb S3 как хостинг статики — отличное решение. Но с кастомными доменами придётся извернуться. Надеюсь, когда-нибудь они добавят эту возможность, но пока — nginx.