Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions docs/project-work/deploy-na-railway.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
title: "Как задеплоить проект на Railway"
---

[Перейти на сайт](https://ru.hexlet.io)

# Как задеплоить проект на Railway

> Бесплатный деплой приложения из репозитория GitHub вместе с базой PostgreSQL без банковской карты.

Railway собирает приложение прямо из репозитория на GitHub и выдаёт ему публичный адрес. Бесплатного тарифа хватает, чтобы показать проект наставнику, и карта для него не нужна. Инструкция проверена 11 ноября 2026 года на двух учебных приложениях. В [react-todo-app-with-backend](https://github.com/hexlet-components/react-todo-app-with-backend) фронтенд на React раздаёт сервер на Fastify, а [java-javalin-blog](https://github.com/hexlet-components/java-javalin-blog) это блог на Javalin с базой PostgreSQL.

Как выбрать сервис и чем Railway отличается от остальных, рассказано в статье [Куда задеплоить учебный проект](kuda-deploit-proekt.md).

## Что понадобится

Нужен аккаунт на [GitHub](https://github.com/) и репозиторий с приложением. Аккаунт на Railway создаётся входом через GitHub, отдельная регистрация не нужна.

## Подготовьте приложение

Общие требования к приложению описаны в статье [Куда задеплоить учебный проект](kuda-deploit-proekt.md). Railway сам выбирает порт и передаёт его приложению в переменной `PORT`.

Если у проекта есть фронтенд, его собирает скрипт `build` в `package.json`, а готовые файлы раздаёт сервер. Railway выполняет `build` перед запуском сам, поэтому собранную папку (`dist` у Vite) в репозиторий класть не нужно. Сервер запускается скриптом `start`.

```json
{
"scripts": {
"build": "vite build",
"start": "node bin/server.js"
}
}
```

Раздачу фронтенда сервером показывает [исходная статья в блоге Хекслета](https://ru.hexlet.io/blog/posts/railway). Там использован устаревший create-react-app, у Vite папка сборки называется `dist` вместо `build`, остальное работает так же.

## Войдите и проверьте тариф

Откройте [railway.com](https://railway.com/) и нажмите **Continue with GitHub**. Новый аккаунт получает пробный период, его хватает для деплоя.

Если вы уже регистрировались на Railway раньше, пробный период мог закончиться. Тогда Railway не даст создать проект и напишет «Your trial has expired». Откройте **Settings → Plans** и нажмите **Downgrade to Free** внизу страницы. Тариф Free включается без карты.

![Страница тарифов Railway после перехода на Free, 1 доллар кредитов в месяц, до 1 vCPU и 0,5 ГБ памяти на сервис, 0,5 ГБ хранилища](/img/docs/img-220--railway-free-plan.png)

## Создайте проект

На главной странице Railway нажмите **New** и выберите **GitHub Repository**.

![Меню нового проекта в Railway, первый пункт GitHub Repository](/img/docs/img-221--railway-new-project.png)

Начните вводить имя репозитория и выберите его в списке.

![Поиск репозитория в Railway, по запросу react-todo найдены два репозитория](/img/docs/img-222--railway-select-repo.png)

Если нужного репозитория в списке нет, у Railway нет к нему доступа. Очистите строку поиска, выберите **Configure GitHub App** и на странице GitHub дайте доступ к этому репозиторию. Достаточно отметить **Only select repositories** и выбрать один репозиторий.

После выбора репозитория Railway создаёт сервис и запускает сборку. Если в корне репозитория лежит `Dockerfile`, Railway собирает образ по нему. Если нет, сам определяет Node.js, устанавливает зависимости, выполняет `build` и запускает `start`. Версию Node.js он берёт из поля `engines` в `package.json` или из файла `.nvmrc`.

## Проверьте сборку

Сборка занимает около минуты. Ход сборки показывает вкладка **Deployments**, а **View logs** открывает журналы. Во вкладке **Build Logs** видна установка зависимостей и сборка, во вкладке **Deploy Logs** вывод запущенного приложения.

Сборка закончена, когда у деплоя стоит статус **ACTIVE**, а у сервиса **Online**.

![Вкладка Deployments в Railway, деплой со статусом ACTIVE и подписью Deployment successful, сервис Online](/img/docs/img-223--railway-deployment-active.png)

## Получите адрес

Пока у сервиса нет адреса, из интернета он недоступен. Откройте **Settings**, найдите раздел **Networking** и нажмите **Generate Domain**. Railway выдаст адрес вида `<имя-сервиса>-production.up.railway.app`.

![Раздел Networking в настройках сервиса, выданный домен на up.railway.app, порт 8080 и кнопка Generate Domain](/img/docs/img-224--railway-generate-domain.png)

Откройте адрес в браузере. Если приложение работает, этот адрес отправляйте наставнику.

![Учебное приложение Hexlet Todos, открытое по адресу на Railway, с добавленной задачей](/img/docs/img-225--railway-app-online.png)

После каждого пуша в ветку `main` Railway пересобирает и перезапускает приложение сам.

## Подключите базу PostgreSQL

База на Railway работает отдельным сервисом в том же проекте, что и приложение. Откройте проект, нажмите **Add**, выберите **Database** и затем **PostgreSQL**.

![Меню Database в Railway, первый пункт PostgreSQL](/img/docs/img-226--railway-add-postgres.png)

Railway создаёт сервис Postgres вместе с томом `postgres-volume`, на котором лежат данные. Они сохраняются после перезапуска базы и после новых деплоев приложения. Мы проверили это перезапуском сервиса Postgres, и созданная перед этим запись осталась на месте.

![Проект в Railway с тремя сервисами, Postgres с томом postgres-volume и два приложения](/img/docs/img-227--railway-postgres-and-app.png)

Из интернета база недоступна, к ней подключаются только сервисы того же проекта. Адрес и пароль приложение получает через переменные окружения. Railway публикует у сервиса базы переменные `PGHOST`, `PGPORT`, `PGDATABASE`, `PGUSER`, `PGPASSWORD` и `DATABASE_URL`, а в переменной приложения на них ссылаются записью `${{Postgres.ИМЯ}}`. Переменные задаются во вкладке **Variables** сервиса приложения кнопкой **New Variable**.

Многие библиотеки для Node.js и Python принимают `DATABASE_URL` как есть. Тогда в приложении достаточно завести переменную `DATABASE_URL` со значением `${{Postgres.DATABASE_URL}}`.

Java-приложению нужен адрес в формате JDBC, а `DATABASE_URL` записан в формате `postgresql://`, поэтому JDBC-адрес собирают из отдельных переменных. В блоге на Javalin адрес читается из переменной `JDBC_DATABASE_URL`.

```text
jdbc:postgresql://${{Postgres.PGHOST}}:${{Postgres.PGPORT}}/${{Postgres.PGDATABASE}}?user=${{Postgres.PGUSER}}&password=${{Postgres.PGPASSWORD}}
```

![Вкладка Variables сервиса приложения, заведена переменная JDBC_DATABASE_URL](/img/docs/img-228--railway-jdbc-variable.png)

Сохраните переменную и запустите деплой кнопкой **Deploy** вверху проекта. В журнале запуска появится строка о подключении к базе, у блога это сообщение пула соединений HikariCP со словом `Start completed`.

## Java-приложение

Блог на Javalin собирается по `Dockerfile` из корня репозитория. Образ ставит JDK и Gradle, выполняет `gradle installDist` и запускает собранное приложение. Сборка заняла около полутора минут.

Сервер Java-приложения тоже должен слушать порт из переменной `PORT` и адрес `0.0.0.0`. В блоге порт читается так.

```java
String port = System.getenv().getOrDefault("PORT", "3000");
```

Памяти тарифа Free хватает. Блог на Javalin после запуска занимал около 160 МБ из 0,5 ГБ, и настраивать JVM не понадобилось. Расход памяти показывает вкладка **Metrics** сервиса. Если график подходит к пределу, ограничьте кучу JVM переменной `JAVA_TOOL_OPTIONS` со значением `-XX:MaxRAMPercentage=60`.

![Вкладка Metrics сервиса на Javalin, память около 160 МБ при пределе 0,5 ГБ](/img/docs/img-229--railway-java-metrics.png)

![Блог на Javalin, открытый по адресу на Railway, со статьёй из базы PostgreSQL](/img/docs/img-230--railway-java-blog.png)

Блог пишет журнал в поток ошибок, как и многие Java-приложения с логгером по умолчанию, поэтому Railway подсвечивает обычные строки `INFO` красным. Это не ошибка, смотрите на текст сообщения.

## Ограничения бесплатного тарифа

На тарифе Free сервису выделяется 1 vCPU и 0,5 ГБ памяти, а в месяц начисляется кредитов на 1 доллар. Этого хватает, чтобы показать учебный проект, но держать приложение постоянно не получится. Приложение без запросов засыпает, и первый запрос после паузы открывается дольше обычного.

Тому базы данных на Free отводится 0,5 ГБ, учебной базе этого хватает с запасом. Каждый сервис расходует кредит отдельно, поэтому приложение с базой тратит его быстрее одного приложения.

Число проектов на Free ограничено. Если Railway отвечает «Free plan resource provision limit exceeded», удалите ненужный проект в **Settings → Danger → Delete Project** у самого проекта. Свой домен на этом тарифе подключить нельзя.

## Если что-то пошло не так

Сначала откройте журналы деплоя, причина почти всегда написана там.

- **Сервис падает сразу после старта, в журнале `Killed` и код 137.** Приложению не хватило 0,5 ГБ памяти. Так бывает, например, с базой данных, которая работает внутри процесса приложения, вроде PGlite.
- **Статус Online, а адрес не открывается.** Сервер слушает `127.0.0.1` или фиксированный порт. Проверьте `host: '0.0.0.0'` и `process.env.PORT`.
- **Фронтенд не открывается, а API отвечает.** Не выполнилась сборка фронтенда. Проверьте скрипт `build` в `package.json` и путь, по которому сервер раздаёт собранные файлы.
- **Railway пишет «Free plan resource provision limit exceeded».** Лимит проектов исчерпан. Удалите старый проект или добавьте сервис в существующий.
Loading
Loading