Как задеплоить Rust-приложение: Axum, Actix или Rocket

Rust-приложение можно выложить в сеть, не арендуя сервер: загружаете исходники вместе с Cargo.toml, платформа видит Rust-проект, собирает его и выдаёт публичную HTTPS-ссылку. Условие одно и оно решающее: приложение должно слушать адрес 0.0.0.0 и порт, который приходит в переменной PORT, а не 127.0.0.1 и не число, вписанное в код. И сразу настройтесь на главную особенность — первая сборка Rust длится дольше, чем у любого другого поддерживаемого языка: компилятор собирает каждую зависимость с нуля в release-режиме. Это норма, а не поломка. Ниже — путь от папки с кодом до рабочей ссылки на Dockbox и то, где Rust-проекты спотыкаются чаще всего.

Почему Rust-проект по умолчанию тащат на VPS

Привычный сценарий начинается с аренды сервера: поставить тулчейн, собрать проект, поставить перед ним nginx, выпустить сертификат и придумать, как поднимать процесс после падения и перезагрузки. Rust компилируется в один исполняемый файл — сам язык тут не сложный. Сложно всё, что вокруг файла, и обычно это ложится на вас.

Тулчейн и версия компилятора. rustup, edition, ночные сборки — всё это живёт на сервере, пока вы его не снесёте. Обновление Rust под новую зависимость может сломать сборку, и разбираться в этом придётся на живой машине.

Сборка на сервере. cargo build --release ест CPU и память, а линковка релизного бинарника легко требует больше гигабайта RAM. На дешёвом VPS процесс просто убивает OOM-killer, и вы видите невнятную ошибку вместо результата.

Обратный прокси и сертификат. Без nginx приложение не отдаёт HTTPS, а сертификат надо продлевать каждые три месяца.

Автозапуск и перезапуск. Чтобы бинарник вернулся в строй после падения и после перезагрузки, нужен systemd-юнит, и его отладка — отдельный навык.

Оплата сервера независимо от нагрузки. Машина стоит денег все 30 дней, даже если сервис дёргают раз в сутки.

Платформа деплоя снимает эту часть: вы загружаете папку с исходниками, она находит Cargo.toml, ставит тулчейн, собирает проект и отдаёт публичную HTTPS-ссылку. Ни сервера, ни nginx, ни ручного сертификата.

Шаг 1. Выберите фреймворк: Axum, Actix Web или Rocket

Для платформы выбор фреймворка не имеет значения: проект распознаётся по Cargo.toml, а не по названию библиотеки. Но выбирать всё равно придётся, поэтому коротко о каждом.

Axum — минималистичный фреймворк из экосистемы tokio, собранный на башнях-мидлварях. Хороший выбор для нового проекта: понятный API, отличная документация, легко подключать готовые мидлвари.

Actix Web — зрелый и очень быстрый, один из лидеров бенчмарков. Своя модель с акторами и App, чуть больше «магии», зато огромная база примеров.

Rocket — самый выразительный синтаксис, приятен для прототипов и небольших API. Нюанс один: конфигурацию с портом лучше собирать в коде, а не держать в Rocket.toml, иначе порт из окружения до приложения не доедет.

Требование к приложению одинаковое для всех трёх — адрес 0.0.0.0 и порт из переменной окружения. Всё остальное — вкусовщина.

Шаг 2. Отдайте исходники, а не папку target

Сборка идёт на стороне платформы, поэтому нужен исходный код: Cargo.toml, папка src и остальные файлы проекта. Папку target в архив не кладите — это результат вашей локальной сборки, она очень большая и на нашей стороне её всё равно пересоздадут.

Бинарник, скомпилированный на вашем ноутбуке, тоже не нужен: собранный под macOS или Windows, он на Linux-сервере не запустится вообще. А вот Cargo.lock оставьте в архиве — он фиксирует версии зависимостей, и сборка получится воспроизводимой.

Складывайте архив так, чтобы Cargo.toml оказался в корне, а не внутри лишней папки-обёртки.

Шаг 3. Читайте порт из PORT и слушайте 0.0.0.0

Порт задаёт платформа, а не вы. Значение приходит в переменной PORT, и его нужно прочитать в рантайме. В Rust это std::env::var, а адрес для привязки — 0.0.0.0, иначе приложение отвечает только внутри контейнера и ссылка останется пустой.

Плохой вариант — порт и адрес вписаны в код:

let listener = TcpListener::bind("127.0.0.1:8000").await?;

Правильный — адрес собирается из переменной окружения:

let port: u16 = env::var("PORT")?.parse()?;
let addr = format!("0.0.0.0:{port}");
let listener = TcpListener::bind(addr).await?;

В Axum это адрес в TcpListener::bind, в Actix Web — аргумент метода bind у HttpServer. С Rocket чуть иначе: конфиг удобнее собрать в коде, чем править Rocket.toml:

let port: u16 = env::var("PORT")?.parse()?;
let config = rocket::Config {
    port,
    address: "0.0.0.0".parse()?,
    ..Default::default()
};
rocket::custom(config).launch()

Шаг 4. Ключи и токены — в переменные окружения

Ключи API, токены и строки подключения к базе читайте через std::env::var, а не держите константами в коде. Код вы почти наверняка зальёте в Git или кому-то покажете, а вместе с ним уедут и секреты.

let db_url = env::var("DATABASE_URL")
    .expect("DATABASE_URL is not set");

В настройках проекта Dockbox запрашивает такие значения и передаёт их в контейнер в зашифрованном виде, поэтому в архиве и в репозитории они не появляются. Файл .env при этом включать не нужно.

Шаг 5. Загрузите код и дождитесь первой сборки

Проект попадает на платформу архивом или импортом репозитория GitHub, в том числе приватного. Дальше начинается самое честное предупреждение этого материала: первая сборка Rust-проекта — самая долгая из всех поддерживаемых языков. Компилятор собирает не только ваш код, но и каждую зависимость с нуля, в release-режиме с оптимизациями. Сборка на несколько минут — это ожидаемое поведение, а не признак, что что-то сломалось.

Статус и живые логи в панели показывают, что сборка идёт. Повторные сборки обычно бывают быстрее: часть зависимостей уже закеширована. Новая версия уезжает тем же путём — свежий архив или обновление из GitHub, а ссылка остаётся прежней.

Сколько памяти нужно Rust-сервису и сколько это стоит

Это статически скомпилированный бинарник без интерпретатора и виртуальной машины, поэтому в простое веб-сервис на Axum или Actix ест десятки мегабайт, а не сотни. Основной аппетит приходится на этап компиляции: линковка release-бинарника может потребовать больше гигабайта RAM, и на слабой машине именно сборка, а не работа, упирается в потолок.

Ни администрировать сервер, ни следить за процессом не нужно: контейнеры работают постоянно, без ограничения по часам и без «засыпания». Упавший контейнер платформа перезапускает сама.

Starter — 990 ₽/мес. 1 vCPU, 2 GB RAM, один проект. Первый месяц бесплатный. Для одного Rust-сервиса этого достаточно, и лимита по времени работы у тарифа нет.

Pro — 2 490 ₽/мес. 2 vCPU, 4 GB RAM, до пяти проектов — вариант, если сборка упирается в память или приложений несколько.

Team — 4 990 ₽/мес — для команд, которым нужен запас ресурсов и SLA.

Свой Dockerfile поддерживается, но не обязателен: язык и стек платформа определяет по коду сама. Если Dockerfile нужен, берите многоступенчатую сборку, чтобы образ остался маленьким:

FROM rust:1.83 AS build
WORKDIR /src
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
COPY --from=build /src/target/release/myapp /usr/local/bin/myapp
CMD ["myapp"]

Частые вопросы

Почему первая сборка Rust идёт так долго?

Компилятор собирает не только ваш код, но и каждую зависимость с нуля, в release-режиме с оптимизациями, — поэтому это самая долгая сборка из всех поддерживаемых языков. Это ожидаемо: прогресс видно в логах панели. Последующие сборки обычно заметно быстрее.

Нужно ли компилировать бинарник самому и загружать файл?

Нет, загружайте исходники: Cargo.toml, папку src и остальные файлы проекта. Сборка происходит на нашей стороне, поэтому Rust и тулчейн локально не нужны. Бинарник, собранный на macOS или Windows, на сервере не запустился бы в любом случае.

Работают ли Axum, Actix Web и Rocket?

Да, фреймворк может быть любым: проект распознаётся по Cargo.toml, а не по названию библиотеки. Требование одно для всех — слушать 0.0.0.0 и порт из переменной PORT. С Rocket проще всего собрать конфигурацию в коде, а не через Rocket.toml.

Что делать, если в проекте несколько бинарников?

Укажите главный через default-run в Cargo.toml, чтобы не было двусмысленности с тем, что запускать. То же самое касается проекта из нескольких пакетов: организуйте его так, чтобы точка входа была одна. Иначе сборка может выбрать не тот бинарник.

Сборка прошла, а ссылка не открывается. Что проверить?

Начните с порта и адреса: приложение должно слушать 0.0.0.0 и порт из переменной PORT, а не 127.0.0.1 и число из кода. Вторая частая причина — падение сразу после старта, например из-за отсутствующего ключа или недоступной базы. И то и другое видно в логах панели, вместе с текстом ошибки и моментом остановки процесса.