Як я переїхав з блогом на Railway

Підписка на старому хостингу закінчилась, а бекапу не було. Розповідаю, як я перезапустив блог на Railway і на які граблі наступив.

·5 хв читання
Як я переїхав з блогом на Railway

Цей блог раніше жив на іншому хостингу. У якийсь момент підписка закінчилась, і разом із нею зникли база даних і всі завантажені картинки. Бекапу в мене не було. Тож перший висновок із цієї історії з’явився ще до самого переїзду: навіть для маленького особистого проєкту бекапи — це не формальність.

Добре, що чернетки статей частково збереглися в моїх нотатках, тож тексти вдалося відновити. А от інфраструктуру довелося піднімати з нуля. Я вирішив використати це як нагоду зробити все трохи акуратніше, ніж було.

Чому саме Railway

Від нового хостингу мені було потрібно кілька речей:

  • деплой з GitHub після кожного push, без ручного налаштування сервера;
  • керований Postgres поруч із застосунком;
  • постійне сховище для картинок;
  • сервери в Європі;
  • можливість описати інфраструктуру кодом, а не тільки клацати в панелі.

Railway закриває все це з коробки, тому довго я не вибирав.

Що потрібно блогу

Сам блог — це Nuxt 3 з SSR, Postgres через Prisma, вхід через Google і завантаження картинок на диск. Тобто з боку інфраструктури мені потрібні три речі: Node-сервіс, база даних і диск, який не зникає між деплоями.

Як це влаштовано

У проєкті Railway зараз два сервіси, обидва в регіоні EU West (Амстердам):

  • web — збирається з GitHub-репозиторію через Dockerfile. Кожен push у main автоматично запускає новий деплой.
  • Postgres — керована база Railway зі своїм volume. У змінних сервісу web рядок підключення не копіюється вручну, а підставляється посиланням: DATABASE_URL=${{Postgres.DATABASE_URL}}.

Dockerfile багатоступеневий: окремо встановлюються залежності, окремо збирається Nuxt, а у фінальний образ потрапляють тільки прод-залежності та зібраний Nitro. Під час старту контейнера спочатку застосовуються міграції, а потім запускається сервер:

CMD ["sh", "-c", "npx prisma migrate deploy && exec node .output/server/index.mjs"]

Демо-даних на проді немає: сід я запускаю тільки локально.

Картинки зберігаються на volume, змонтованому в /data, а шлях до них задається змінною NUXT_UPLOAD_DIR=/data/uploads. Без volume всі завантажені файли зникали б із кожним деплоєм, бо файлова система контейнера тимчасова. Після історії зі старим хостингом це було б особливо іронічно.

Для healthcheck я використовую /robots.txt — легкий маршрут, який відповідає швидко і показує, що сервер справді піднявся.

Інфраструктура як код

Спочатку я описав деплой у railway.json, але майже одразу з’ясувалося, що Railway перестає підтримувати цей формат з 1 грудня 2026 року. Тому я переніс конфіг у .railway/railway.ts на їхньому TypeScript SDK:

import { defineRailway, github, project, service, volume } from "railway/iac";

export default defineRailway(() => {
  const uploads = volume("web-volume", { region: "ams", sizeMB: 5000 });

  const web = service("web", {
    source: github("killingHail606/zhurbiy-com", { branch: "main" }),
    build: { builder: "DOCKERFILE", dockerfilePath: "Dockerfile" },
    healthcheck: "/robots.txt",
    replicas: { ams: 1 },
    domains: ["zhurbiy.com", "www.zhurbiy.com"],
    volumeMounts: { "/data": uploads },
  });

  return project("zhurbiy-com", {
    variables: { managed: false },
    resources: [web, uploads],
  });
});

Кілька речей, які я для себе зафіксував:

  • Змінні середовища не керуються через IaC (variables: { managed: false }). Так секрети взагалі не потрапляють у репозиторій, а значення живуть тільки в Railway.
  • Завжди дивитися план змін перед застосуванням: спочатку railway config plan, потім railway config apply. Якщо випадково прибрати volume з конфігу, apply від’єднає його від сервісу разом із картинками.
  • Для команд IaC потрібен Node 22+. Локально в мене за замовчуванням стояв Node 18, тому ці команди я запускаю з новішою версією.

Граблі, на які я наступив

Змінні, які вшиваються під час збірки

Частина конфігурації Nuxt читається не під час роботи сервера, а під час npm run build. У моєму випадку це адреса для авторизації (NUXT_AUTH_ORIGIN) і адреса сайту (SITE_URL). У Dockerfile вони передаються як ARG. Наслідок простий, але неочевидний: після зміни цих змінних потрібен новий білд. Простий рестарт сервісу нічого не змінить.

Решта runtime-конфігу підхоплюється через змінні з префіксом NUXT_, тож там достатньо рестарту.

Регіони і репліки

У IaC регіон Амстердама називається ams, а в команді railway scale — eu-west. Ще важливіше, що railway scale додає регіон, а не переносить сервіс. Якщо не вимкнути стару репліку явно, сервіс просто працюватиме у двох регіонах одночасно. Для застосунку це зайві гроші, а для бази — ще й дві репліки там, де мала бути одна.

Cloudflare і нескінченні редиректи

DNS домену в мене на Cloudflare: два CNAME-записи на домени Railway і TXT-записи _railway-verify, які Railway використовує для перевірки домену. Їх видаляти не можна.

Проксі Cloudflare (оранжева хмарка) можна лишити ввімкненим, але режим SSL/TLS має бути Full або Full (strict). У режимі Flexible Cloudflare ходить до Railway по HTTP, Railway перенаправляє на HTTPS, і сайт потрапляє в нескінченний цикл редиректів.

Google-вхід на новому домені

Після переїзду вхід через Google не запрацює, поки в Google Cloud Console немає redirect URI для нового домену. У моєму випадку це https://zhurbiy.com/api/auth/callback/google. Для локальної розробки там же лежить окремий URI з localhost.

Секрети в консолі

Railway CLI охоче друкує значення змінних прямо в термінал, наприклад під час railway variables. Тому секрети я тепер задаю так, щоб вивід нікуди не потрапляв:

railway variables --service web --set "KEY=$VALUE" > /dev/null

Інакше ключі легко опиняються в історії терміналу або в логах, а потім їх доводиться перевипускати.

Що я зробив би інакше

  • Налаштував би бекапи бази й картинок з першого дня. Зараз це перший пункт у моєму списку.
  • Одразу описав би інфраструктуру кодом, а не починав з формату, який скоро перестане підтримуватися.
  • Одразу продумав би домен з www. Редирект з www на основний домен я ще маю налаштувати в Cloudflare.

Підсумок

Переїзд вийшов вимушеним, але корисним. Тепер блог деплоїться сам після кожного push, база й картинки живуть на постійних дисках в Європі, а вся конфігурація описана кодом і лежить у репозиторії. І головне, що я виніс з цієї історії: інфраструктура особистого проєкту заслуговує на таку ж увагу, як і робоча. Особливо коли йдеться про бекапи.

Читати мовою:English

Коментарі 0

щоб залишити коментар

Коментарів ще немає. Будьте першими.