Actix Web is a mature, extremely fast Rust web framework built on the actor model and Tokio. Deploying a Rust API has a different rhythm than a scripting language: the build is heavier (you compile a binary), but the result is a tiny, dependency-free artifact that starts in milliseconds and uses very little memory. The multi-stage Docker build is the key technique.
A minimal Actix Web service
# Cargo.toml
[dependencies]
actix-web = "4"
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] }// src/main.rs
use actix_web::{get, web, App, HttpServer, Responder, HttpResponse};
use sqlx::postgres::PgPoolOptions;
#[get("/health")]
async fn health() -> impl Responder {
HttpResponse::Ok().json(serde_json::json!({ "status": "ok" }))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL not set");
let pool = PgPoolOptions::new()
.max_connections(10)
.connect(&db_url)
.await
.expect("db connect failed");
let port: u16 = std::env::var("PORT").ok()
.and_then(|p| p.parse().ok())
.unwrap_or(8080);
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(pool.clone()))
.service(health)
})
.bind(("0.0.0.0", port))? // bind to all interfaces
.run()
.await
}Two production essentials are already here: bind to 0.0.0.0 (not 127.0.0.1, or the platform proxy can't reach you) and read PORT from the environment.
The multi-stage build
The whole art of deploying Rust is getting a small final image. Compile in a full Rust toolchain stage, then copy only the binary into a minimal runtime image:
FROM rust:1-slim AS build
WORKDIR /app
# Cache dependencies separately for faster rebuilds
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo 'fn main(){}' > src/main.rs && cargo build --release && rm -rf src
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /app/target/release/myapi /usr/local/bin/myapi
EXPOSE 8080
CMD ["myapi"]The dependency-caching trick (build a dummy main.rs first) means edits to your source don't re-download and recompile every crate. ca-certificates is needed for TLS connections to the database.
For an even smaller image, statically link against musl and ship on scratch or gcr.io/distroless/static — but debian-slim is the pragmatic default.
Database access with sqlx
sqlx gives you async, compile-time-checked SQL. Run queries against the pool:
let rows = sqlx::query!("SELECT id, title FROM posts LIMIT 20")
.fetch_all(&pool)
.await?;For migrations, sqlx has a CLI:
sqlx migrate add create_posts # create a migration
sqlx migrate run # apply (run as a deploy step)Apply migrations once per deploy before the binary serves traffic. If you use compile-time query checking (query!), set DATABASE_URL at build time or commit the offline query cache (sqlx prepare) so CI builds don't need a live database.
Deploying on PandaStack
- 1Provision a managed PostgreSQL (14.x or 16.x). PandaStack injects
DATABASE_URL; the binary reads it from the environment. - 2Connect the Git repo as a container app. The Dockerfile is auto-detected — the reliable path for Rust.
- 3The platform sets
PORT; the code already reads it. Bind to0.0.0.0(done above). - 4Run
sqlx migrate runonce as a deploy step. - 5Add a custom domain; SSL is automatic.
Rust's tiny memory footprint and instant startup are ideal for scale-to-zero — a cold start is essentially the time to launch one small binary. That makes Actix a great fit for cost-efficient lower tiers.
Production checklist
- [ ] Multi-stage build, minimal runtime image.
- [ ]
ca-certificatesinstalled for DB TLS. - [ ] Bind
0.0.0.0, readPORT. - [ ] sqlx offline cache committed if using compile-time checks.
- [ ] Migrations run as a deploy step.
- [ ]
--releasebuild (debug builds are far slower).
Verifying
curl -s https://api.example.com/health
# {"status":"ok"}A healthy response from the compiled binary means your build and DB wiring are correct.
References
- [Actix Web documentation](https://actix.rs/docs/)
- [sqlx repository and docs](https://github.com/launchbadge/sqlx)
- [Rust official Docker images](https://hub.docker.com/_/rust)
- [Distroless container images](https://github.com/GoogleContainerTools/distroless)
---
A tiny Rust binary with instant startup is exactly what scale-to-zero rewards — PandaStack auto-detects your Dockerfile and injects DATABASE_URL from a managed PostgreSQL. Start free at [dashboard.pandastack.io](https://dashboard.pandastack.io).