Rust
Deploy a Rust app on Ruust: Axum or Actix, detected from Cargo.toml, compiled in release mode and served on HTTPS.
Ruust hands your repo to Nixpacks, whose Rust provider recognises the app from a Cargo.toml file at the repo root. It reads your crate and toolchain, runs a release build with cargo build --release, and hatches the resulting binary as an Egg. This works the same way whether your server is built on Axum or Actix Web: both read the port from the environment and listen on a socket, which is all Ruust needs.
Deploy it
- Commit
Cargo.toml(andCargo.lock) at the repo root and push to a Git repo. - In the dashboard, choose New Egg and connect the repo.
- Pick a size and a region (London or Virginia), then lay the Egg.
- Ruust runs
cargo build --releaseand hatches the compiled binary.
The port
Ruust sets a PORT environment variable. Your server must read it and bind to 0.0.0.0, not localhost. Binding to the wrong address is the top reason an Egg builds but never hatches: the process is up, but nothing outside the container can reach it. Read PORT with std::env::var and construct the bind address from it.
use std::env;
use axum::{routing::get, Router};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "ok" }));
let port: u16 = env::var("PORT")
.unwrap_or_else(|_| "3000".to_string())
.parse()
.expect("PORT must be a number");
let listener = TcpListener::bind(("0.0.0.0", port)).await.unwrap();
axum::serve(listener, app).await.unwrap();
}Actix Web
Actix follows the same rule: pass a (host, port) tuple to bind, with the host set to 0.0.0.0. Read PORT from the environment exactly as above.
use std::env;
use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder {
"ok"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let port: u16 = env::var("PORT")
.unwrap_or_else(|_| "3000".to_string())
.parse()
.expect("PORT must be a number");
HttpServer::new(|| App::new().route("/", web::get().to(index)))
.bind(("0.0.0.0", port))?
.run()
.await
}The start command
Nixpacks runs the release binary for you. To set the start command yourself, add a Procfile at the repo root with a web: line: on the Nixpacks path that line is read by Nixpacks and baked into the image as the start command (the Ruust workload spec carries no start-command field for the run path, and the Procfile is ignored when you supply your own Dockerfile). The binary lives at target/release/<name>, where the name matches the [[bin]] (or package) name in your Cargo.toml.
web: ./target/release/appEnvironment variables
Set env vars on the Egg. They are available at both build and run time, encrypted at rest, and never printed in logs. Read them with std::env::var at run time. Rust ships no client bundle, so there is no public prefix to worry about: everything stays server-side.
Builds are slower
Rust compiles ahead of time, so a release build takes noticeably longer than an interpreted stack. That cost is paid at build time only: once the Egg hatches, the binary starts fast and runs lean.
FROM rust:1.79 AS build
WORKDIR /src
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo build --release
FROM gcr.io/distroless/cc-debian12
COPY --from=build /src/target/release/app /app
ENV PORT=3000
EXPOSE 3000
ENTRYPOINT ["/app"]