Runway
Runway is built and operated in the EU!

Rust (axum)

First steps

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.

cargo init runway-rust
cd runway-rust
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:

Dockerfile
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.

Create the app on Runway (this also initializes a git repository, if none exists yet), then commit your changes:

runway app create
git add -A && git commit -m "initial commit"

Deploy to Runway

runway app deploy

Run runway app open to see it live, or check the Runway UI. Check runway app logs if something’s off.

Congrats, young Rustacean! You deployed your application and it’s running on Runway! 🦀🦀🦀