Rust
A Cargo.toml is enough. The two things to plan for are build time and which binary runs.
A Cargo.toml at the root is enough.
marina vessels create api --repo acme/api Your crate is compiled and the resulting binary is what runs, named after the package. Nothing is interpreted at start, so the running app is small and boots fast; starter is usually plenty.
The port
Marina sets PORT and routes to it. Read it, and bind all interfaces:
let port: u16 = std::env::var("PORT")
.ok()
.and_then(|p| p.parse().ok())
.unwrap_or(3000);
let listener = tokio::net::TcpListener::bind(("0.0.0.0", port)).await?; 0.0.0.0, not 127.0.0.1. The second one builds, starts, logs nothing unusual, and answers no requests.
The Rust version
From rust-toolchain.toml, then the rust-version field in Cargo.toml. Pin it if your code uses something recent; without one you get a version that will move under you.
[toolchain]
channel = "1.89" Build time
A cold Rust build is minutes, not seconds, and it is the slowest first deploy of any stack here. Nothing is wrong; it is the compiler.
Two things make it bearable. Keep the dependency tree honest, because most of that time is dependencies rather than your code. And remember a failed or slow build never takes the running app down: the previous version serves throughout.
The thing that usually breaks
A workspace with more than one binary. The binary is chosen from the package, so a workspace that builds several gets one of them, and it may not be the server.
A Dockerfile at the root is used automatically and says exactly what to build:
FROM rust:1.89 AS build
WORKDIR /src
COPY . .
RUN cargo build --release --bin server
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY /src/target/release/server /server
CMD ["/server"] PORT is still injected, and ca-certificates is there because a binary that makes outbound HTTPS calls fails without it in a slim image, which is a confusing way to lose an afternoon.
