Back to Blog
Tutorial10 min read2026-06-29

How to Deploy an Actix Web Rust API to Production

Actix Web is one of the fastest Rust web frameworks. This guide covers a multi-stage Rust build for a tiny container image, binding correctly, connecting PostgreSQL with sqlx, and deploying to production.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

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

  1. 1Provision a managed PostgreSQL (14.x or 16.x). PandaStack injects DATABASE_URL; the binary reads it from the environment.
  2. 2Connect the Git repo as a container app. The Dockerfile is auto-detected — the reliable path for Rust.
  3. 3The platform sets PORT; the code already reads it. Bind to 0.0.0.0 (done above).
  4. 4Run sqlx migrate run once as a deploy step.
  5. 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-certificates installed for DB TLS.
  • [ ] Bind 0.0.0.0, read PORT.
  • [ ] sqlx offline cache committed if using compile-time checks.
  • [ ] Migrations run as a deploy step.
  • [ ] --release build (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).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also