Let’s deploy an application written in Rust (using the popular axum framework)
The application structure is initialized with cargo init
and the following is an example of the src/main.rs
which contains our entire application - a very straight forward service that responds to /
and /hello-world
.
use axum::{
routing::{get, Router},
response::IntoResponse,
};
use std::env;
use std::net::SocketAddr;
use tower_http::trace::TraceLayer;
use tracing::{info, Level};
use tracing_subscriber;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_max_level(Level::INFO)
.init();
let port = env::var("PORT").unwrap_or_else(|_| "3000".to_string());
let addr = format!("0.0.0.0:{}", port)
.parse::<SocketAddr>()
.expect("Unable to parse socket address");
let app = Router::new()
.route("/", get(handler_root))
.route("/hello-world", get(handler_hello_world))
.layer(TraceLayer::new_for_http()); // Add middleware for logging requests
println!("Listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
// Route handler for "/"
async fn handler_root() -> impl IntoResponse {
info!("Handling request to /");
"Hello"
}
// Route handler for "/hello-world"
async fn handler_hello_world() -> impl IntoResponse {
info!("Handling request to /hello-world");
"Hello World"
}
Since we’re using axum
, our Cargo.toml looks like the following:
[package]
name = "runway-rust"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.6"
tokio = { version = "1", features = ["full"] }
tower = "0.4"
tracing = "0.1" # For logging
tracing-subscriber = "0.3" # For logging configuration
tower-http = { version = "0.4", features = ["trace"] } # For TraceLayer middleware
cargo build
next to generate the Cargo.lock
file (which will keep your dependencies in check when deploying this app).Runway doesn’t (yet?) have a buildpack for Rust, but thanks to our Docker/buildkit-based pipeline, that’s not a showstopper. This flexibility showcases Runway’s versatility in handling just about any application, no matter the language or framework. Whatever you’re building, we’ve got your back!
So, let’s dive in—here’s a multi-stage Dockerfile
, and we’ll walk you through it step by step:
FROM rust:1.81.0-bookworm AS build
# Set the working directory inside the container
WORKDIR /build
COPY Cargo.toml Cargo.lock ./
COPY . .
RUN cargo build --release
# Final image
FROM debian:bookworm-slim
RUN apt update \
&& apt install -y ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /app \
&& chown -R 65534:65534 /app
# Set the user and group to nobody:nobody (in numeric)
USER 65534:65534
# Copy the service binary
COPY --from=build --chown=65534:65534 /build/target/release/runway-rust /app/service
EXPOSE 3000
# Run the compiled binary
CMD ["/app/service"]
The first stage of the Dockerfile
copies all the files into the environment and finally uses cargo build --release
to produce a binary (which is our entire app!).
cargo init runway-rust
, which is why our projet is called runway-rust
and the compiled binary is called runway-rust
.In the next/final stage, we install ca certificates (for good measure and the future) and copy the runway-rust
binary into the /app
directory, set the user/group to the numeric equivalent of nobody
and add an EXPOSE 3000
to tell the orchestrator which port the app runs on.
Create an application on Runway:
$ runway app create
INFO checking login status
INFO created app "formidable-alien"
create app formidable-alien: done
next steps:
* commit your changes
* runway app deploy
* runway open
And deploy:
$ runway app deploy && runway open
Congrats, young Rustacean! You deployed your application and it’s running on Runway! 🦀🦀🦀