Oct 23, 20266 min read

How to Deploy an Axum App to AWS

Running a Rust Axum API on AWS with Encore, ECS on Fargate, AWS Lambda or EC2, including Docker builds, databases, secrets and what each option leaves for you to manage.

Axum compiles to one static binary, which makes the application side of deploying to AWS simple, and most of the work is in what surrounds the binary, such as an image registry, a compute service, a load balancer, the VPC it runs in, a database with credentials and logs you can read. This guide compares Encore, ECS on Fargate, Lambda and EC2 for an Axum API, with code for each.

The 2024 State of Rust Survey found Rust especially popular for server backends, web and networking services and cloud technologies. Rust was again the most admired programming language in the 2025 Stack Overflow Developer Survey, at 72%.

What you need to decide:

  • Whether the app runs as a long-lived container or as a Lambda function.
  • How the image is built and where it is stored.
  • How the database and secrets are created and reach the app.
  • Who writes and maintains the AWS resources around the service.

Ways to deploy Axum to AWS at a glance

CapabilityEncoreECS on FargateLambda with lambda_httpEC2
ComputeProvisioned by Encore in your accountFargate tasksLambda functionsInstances you manage
Image buildBuilt on deployYour Dockerfile and ECRcargo lambda or a zipYour build and copy
DatabaseDatabase::new, provisioned on deployYou create RDSYou create RDS, often with RDS ProxyYou create RDS
NetworkingProvisioned by EncoreYou create themAPI Gateway or a function URLYou create them
Local devencore run starts Postgres locallyYou run PostgresYou run PostgresYou run Postgres

Encore: deploy your existing Axum app with its infrastructure

Encore

Encore combines the Axum service and the infrastructure it declares into one deploy to your own AWS account. You add the encore-dev crate to the Axum app you have, routing stays in Axum, and resources are static declarations that Encore finds through static analysis. It is an infrastructure SDK, not a replacement for Axum.

use axum::{extract::Path, http::StatusCode, routing::{get, post}, Json, Router}; use encore::storage::postgres::Database; use serde::Serialize; // Provisioned locally by encore run and in your AWS account on deploy. static DB: Database = Database::new("users").migrations("./migrations"); #[derive(Serialize)] struct User { id: i32, name: String, } async fn add_user(Path(name): Path<String>) -> StatusCode { match DB.execute("INSERT INTO users (name) VALUES ($1)", &[&name]).await { Ok(_) => StatusCode::CREATED, Err(_) => StatusCode::INTERNAL_SERVER_ERROR, } } async fn get_user(Path(id): Path<i32>) -> Result<Json<User>, StatusCode> { let row = DB .query_opt("SELECT id, name FROM users WHERE id = $1", &[&id]) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .ok_or(StatusCode::NOT_FOUND)?; Ok(Json(User { id: row.get("id"), name: row.get("name") })) } #[tokio::main] async fn main() { encore::initialize(); let app = Router::new() .route("/users/:name", post(add_user)) .route("/users/id/:id", get(get_user)); let addr = std::env::var("ENCORE_LISTEN_ADDR").unwrap_or("0.0.0.0:8080".into()); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); axum::serve(listener, app) .with_graceful_shutdown(encore::shutdown::signal()) .await .unwrap(); std::process::exit(encore::shutdown::drained().await.code()); }
The route syntax is for Axum 0.7, and Axum 0.8 uses /users/{name}.

An encore.service.json next to Cargo.toml names the service and exposes its HTTP API through the gateway:

{ "name": "users", "lang": "rust", "run": { "type": "rust" }, "api": [ { "type": "http", "prefix": "/", "name": "users", "expose": true } ] }

encore run starts Postgres locally, applies the migrations and starts the binary. After you connect your AWS account, Encore provisions the infrastructure in that account on deploy, including the database the code declares.

The setup suits agents working on the repo, since the agent can add a resource and the handler that uses it in one diff and run it locally, while cloud access sits with Encore's IAM role, not the agent.

Without Encore

Without Encore the Axum code is the same apart from the static declarations, and the AWS side is built by hand or with an infrastructure-as-code tool: the ECR repository, the cluster and service, the load balancer, the RDS instance and its security group, the secret holding the password and the task role allowed to read it. The ECS section below shows that route.

Try Encore →

ECS on Fargate

Fargate runs containers without servers to patch, and a multi-stage Dockerfile keeps the image small by compiling in one stage and shipping only the binary:

FROM rust:1.90 AS build WORKDIR /app COPY . . RUN cargo build --release FROM debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \ && rm -rf /var/lib/apt/lists/* COPY --from=build /app/target/release/my-api /usr/local/bin/my-api USER 1000 EXPOSE 8080 CMD ["my-api"]

The image is pushed to ECR and referenced by a task definition:

~/my-api
$ aws ecr create-repository --repository-name my-api$ aws ecr get-login-password | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com$ docker build -t <account>.dkr.ecr.<region>.amazonaws.com/my-api:v1 .$ docker push <account>.dkr.ecr.<region>.amazonaws.com/my-api:v1

Good to know

ECS Express Mode creates the Fargate service, load balancer and scaling from an image, which shortens the first deploy. The database, its secret and the task role the container uses to read it are still separate resources you create.

AWS Lambda with lambda_http

The lambda_http crate runs an Axum Router as a Lambda function, since a router is a tower service:

use axum::{routing::get, Router}; use lambda_http::{run, Error}; async fn health() -> &'static str { "ok" } #[tokio::main] async fn main() -> Result<(), Error> { let app = Router::new().route("/health", get(health)); run(app).await }

Rust needs no managed runtime on Lambda because it compiles to native code and runs on an OS-only runtime, and Cargo Lambda builds and deploys the function.

Good to know

A function runs for at most 15 minutes, and a request through an API Gateway REST API has an integration timeout of 50 milliseconds to 29 seconds, which can be raised for Regional and private APIs at the cost of a lower throttle quota. Many concurrent functions opening Postgres connections usually call for RDS Proxy.

EC2

Copying the binary to an EC2 instance and running it under systemd behind a load balancer is the most manual route, which suits teams that already operate instances and leaves patching, scaling and deploys to you.

How to choose

  • Encore if the Axum app has a database or other resources and you want them created in your AWS account from the Rust code, with the same setup running locally.
  • ECS on Fargate for a single container when someone on the team owns the AWS resources around it.
  • Lambda with lambda_http for small APIs with uneven traffic and short requests.
  • EC2 if you already run instances and want full control.

Rust support in Encore is in beta, and you can sign up for access to deploy an Axum app to your own AWS account.

Frequently asked questions

What is the easiest way to deploy an Axum app to AWS?

For a single container, ECS on Fargate with a multi-stage Docker build is the most common route. Lambda with the lambda_http crate suits small APIs with spiky traffic. Encore fits if the app also needs Postgres or other infrastructure, since a deploy provisions the services and resources in your own AWS account from the Rust code.

How do I build a small Docker image for Axum?

Use a multi-stage Dockerfile: compile with cargo build --release in a rust image, then copy the binary into a slim runtime image such as debian:bookworm-slim or a distroless image, so the final image contains only the binary and its runtime libraries.

Can I run Axum on AWS Lambda?

Yes. The lambda_http crate adapts a tower service such as an Axum Router to Lambda events, and the function is deployed on an OS-only runtime because Rust compiles to a native binary. Cold starts are short for Rust, but a request through an API Gateway REST API has an integration timeout of at most 29 seconds unless you request a higher quota for a Regional API.

How do I connect an Axum app on AWS to RDS Postgres?

Create the RDS instance in the same VPC, store the password in Secrets Manager, allow the service's security group in the database security group and pass the connection details to the container. With Encore, you declare Database::new in your code and Encore provisions the database in your AWS account on deploy.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders