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
[127, 0, 0, 1]binds loopback. Use[0, 0, 0, 0]so the proxy can reach the listener.- Cold builds are slow. Copy
Cargo.tomlandCargo.lockand build a stub first so that editing your source does not rebuild every dependency crate. - glibc versus musl. A binary built on
rust:slim(glibc) will not run onalpine(musl). Keep builder and runtime on the same libc, as the Dockerfile above does.
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.
Related pages
- getting-started.md — your first deploy end to end
- configuration.md —
.previewops.yamlreference - databases.md — connecting a database to a preview
- ui-deploy.md — deploying from the dashboard