Rust on Previewops

Previewops builds whatever your Dockerfile produces and runs it as a container, so Rust needs no special support. What it does need is what every stack needs: read the injected PORT and listen on 0.0.0.0.

Read the port std::env::var("PORT")
Bind all interfaces SocketAddr::from(([0, 0, 0, 0], port))

Dockerfile

FROM rust:1.82-slim AS build
WORKDIR /src
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
COPY --from=build /src/target/release/server /server
ENV PORT=8080
CMD ["/server"]

ENV PORT=8080 is only a default. Previewops overrides it at deploy time, so the app must read the value rather than assume it.


Application code

A typical setup with Axum:

use axum::{routing::get, Router};
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    let port: u16 = std::env::var("PORT")
        .ok()
        .and_then(|p| p.parse().ok())
        .unwrap_or(8080);

    let app = Router::new().route("/", get(|| async { "ok" }));

    // [0, 0, 0, 0] is every interface; [127, 0, 0, 1] would be loopback only.
    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Rust-specific pitfalls


Configuration

If your Dockerfile is not at the repository root, point at it from .previewops.yaml:

provider: cloud-run
dockerfile: ./docker/Dockerfile
port: 8080
ttl_hours: 24

See configuration.md for every available option.


Do not gate startup behind a migration

Language-agnostic, and the hardest failure here to diagnose:

# Avoid this
CMD ["sh", "-c", "run-migrations && start-server"]

If the migration cannot reach the database — a paused instance, an expired connection string, a cold-start timeout — it never exits, the server never starts, and the port never opens. The deploy itself already succeeded, so the preview shows as Live while nothing answers. Run migrations as a separate, visible step instead.