Runway

Rust (axum)

Let’s deploy an application written in Rust (using the popular axum framework)

Example

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

Building

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!).

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.

Deploy to Runway

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! 🦀🦀🦀