---
title: Deploy Rust on Vercel with Docker
description: Build a Rust application with Axum and Docker, then deploy it to Vercel Functions. Learn how to configure environment variables, managed storage, and preview deployments for your project.
url: /kb/guide/deploy-rust-on-vercel-with-docker
canonical_url: "https://vercel.com/kb/guide/deploy-rust-on-vercel-with-docker"
published: 2026-08-11
last_updated: 2026-08-11
authors: Anshuman Bhardwaj
related:
  - /docs/container-registry
  - /docs/cli
  - /docs/vercel-blob
  - /docs/global-config
  - /docs/deployments/environments
  - /docs/services
  - /docs/fluid-compute
  - /docs/cron-jobs
  - /docs/workflows
  - /kb/guide/docker
  - /docs/project-configuration/vercel-json
  - /docs/functions/container-images
  - /docs/frameworks
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Axum is a Rust web framework built on Tokio and Tower. Vercel provides a native Rust runtime in public beta, but a container is useful when you want to run an existing Axum HTTP server, control its Linux dependencies, or preserve an existing Docker build.

Cargo produces a native binary; a multi-stage build can compile the application once and omit the compiler and toolchain from the final image. Add a `Dockerfile.vercel` and set the service `runtime` to `container`. Vercel builds the image, stores it in the [Vercel Container Registry](https://vercel.com/docs/container-registry), and serves it from a Function that autoscales with traffic and scales to zero when idle.

This guide deploys a minimal Axum API, binds it to Vercel's `PORT`, and explains how to use durable external storage instead of the container file system.

## Prerequisites

- A [Vercel account](https://vercel.com)
  
- Docker Desktop or a running Docker daemon
  
- [Vercel CLI](https://vercel.com/docs/cli) (`npm install -g vercel`)
  

## How it works

The project is one Axum application with the following parts:

- `src/main.rs` defines `/` and `/health`, binds to `0.0.0.0` on `$PORT`, and drains on `SIGTERM`.
  
- `Cargo.toml` declares Axum, Tokio, and Serde, and `Cargo.lock` pins their resolved versions.
  
- `Dockerfile.vercel` compiles a release binary in one stage and copies it into a minimal final stage.
  
- `vercel.json` declares a container service and rewrites incoming requests to it.
  
- On deployment, Vercel builds the image and runs it as a Vercel Function.
  

## Steps

### 1\. Initialize the project

Create the project and add the dependencies with the features this guide uses:

```bash
cargo new vercel-docker-rust
cd vercel-docker-rust
cargo add axum
cargo add serde --features derive
cargo add tokio --features macros,net,rt-multi-thread,signal
```

The resulting `Cargo.toml` should look like this. The release profile is optional, but keeps the shipped binary small:

```yaml
[package]
name = "vercel-docker-rust"
version = "0.1.0"
edition = "2024"

[dependencies]
axum = "0.8"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal"] }

[profile.release]
strip = true
lto = "thin"
```

Now, for the HTTP server, update the `src/main.rs`:

```rust
use axum::{routing::get, Json, Router};
use serde::Serialize;
use std::{env, net::SocketAddr};

#[derive(Serialize)]
struct Message {
    message: &'static str,
}

#[derive(Serialize)]
struct Health {
    status: &'static str,
}

async fn hello() -> Json<Message> {
    Json(Message { message: "Hello from Rust on Vercel" })
}

async fn health() -> Json<Health> {
    Json(Health { status: "ok" })
}

#[tokio::main]
async fn main() {
    let port = env::var("PORT").ok().and_then(|v| v.parse().ok()).unwrap_or(80);
    let address = SocketAddr::from(([0, 0, 0, 0], port));

    let app = Router::new()
        .route("/", get(hello))
        .route("/health", get(health));

    let listener = tokio::net::TcpListener::bind(address).await.unwrap();

    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}

async fn shutdown_signal() {
    let mut terminate =
        tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
            .expect("install SIGTERM handler");

    terminate.recv().await;
}
```

The server must bind to `0.0.0.0`, not `127.0.0.1`, and read `PORT`. The fallback to port 80 matches Vercel's default container port.

### 2\. Add `Dockerfile.vercel`

Create the `Dockerfile.vercel` file in the project root:

```dockerfile
FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
WORKDIR /app

FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json

FROM chef AS build
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .
RUN cargo build --release

FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=build /app/target/release/vercel-docker-rust /usr/local/bin/server
ENV PORT=80
ENTRYPOINT ["/usr/local/bin/server"]
```

Compiling dependencies from the Cargo Chef recipe before copying the application code lets Docker reuse the dependency layer. The final stage contains the binary and the C libraries it links against, excludes Cargo and the compiler, and runs as the image's non-root user. The path in the `COPY` line must match the package name in `Cargo.toml`, and pinning an exact toolchain tag instead of `latest-rust-1` makes builds reproducible.

### 3\. Add `vercel.json`

Create a `vercel.json` file to configure your project:

```json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "services": {
    "api": {
      "root": ".",
      "entrypoint": "Dockerfile.vercel",
      "runtime": "container"
    }
  },
  "rewrites": [
    { "source": "/(.*)", "destination": { "service": "api" } }
  ]
}
```

The service selects the container runtime and points to the Dockerfile. The catch-all rewrite sends every public path to the Axum service.

### 4\. Run locally

Run the project through Vercel's local container environment:

```bash
vercel dev -L
```

Use the URL printed by the CLI, usually `http://localhost:3000`:

```bash
curl http://localhost:3000/
curl http://localhost:3000/health
```

### 5\. Deploy to Vercel

Authenticate and create a production deployment:

```bash
vercel login
vercel deploy --prod
```

For Git-based deployments, push the directory to a repository, import it in Vercel, and keep both configuration files at the configured project root.

## Extend your application

You have deployed an Axum API as a native Rust binary. You can now add routes, middleware, authentication, and external data services.

### Environment variables

Add configuration in the project settings or with `vercel env add NAME`. Pull development values locally with `vercel env pull`. Read secrets at runtime with `std::env` rather than baking them in at compile time with `env!`, and do not commit them or include them in the image.

### Database and storage

The container runs on Vercel Functions, where the file system is not persistent. Here are some recommendations for persistent workloads:

| Use case                      | Recommended service                                                                                                  |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Files and uploads             | [Vercel Blob](https://vercel.com/docs/vercel-blob)                                                                   |
| Relational data               | [Marketplace Postgres integration](https://vercel.com/marketplace/category/storage?category=storage&search=postgres) |
| Sessions and caching          | [Marketplace Redis integration](https://vercel.com/marketplace/category/storage?category=storage&search=redis)       |
| Frequently read configuration | [Vercel Global Config](https://vercel.com/docs/global-config)                                                        |

Keep database pools small, build them once during startup, and share them through Axum state so a warm instance can reuse connections.

### Iterate with preview deployments

A connected Git repository receives a unique [Preview Deployment](https://vercel.com/docs/deployments/environments) for each branch push or pull request. Run `vercel deploy` without `--prod` to create the same disposable environment from the CLI.

### Scale automatically with Fluid compute

[Vercel Services](https://vercel.com/docs/services) use [Fluid compute](https://vercel.com/docs/fluid-compute) by default. Instances can process concurrent requests and scale to zero when idle. Active CPU billing stops while code waits on I/O and when no requests are running; memory and invocation charges still apply.

## Troubleshooting

### The binary starts locally but requests return 502

**Cause:** The listener is bound to `127.0.0.1` or a hardcoded port instead of the container interface and the port Vercel routes to.

**Fix:** Bind to `[0, 0, 0, 0]` and parse `PORT`, with `80` as the fallback. `EXPOSE` does not configure routing. ### The final image reports a missing loader or library **Cause:** The binary was linked against libraries that the runtime image does not provide, which commonly happens when build and runtime stages mix Alpine and Debian. **Fix:** Keep the build and runtime images in the same family, or build a static MUSL binary and pair it with a matching static image. Run the exact final image locally before deploying. ### Builds remain slow after small source changes **Cause:** Copying the whole source tree before compiling dependencies invalidates the Cargo layer cache on every edit. **Fix:** Keep the Cargo Chef planner and `cargo chef cook` stages ahead of the source build, and commit `Cargo.lock` so dependency resolution is reproducible. ### Spawned Tokio tasks stop before completion **Cause:** Tasks detached with `tokio::spawn` assume the process outlives the response. Scale-in can send `SIGTERM` at any point after a request finishes. **Fix:** Finish request work before responding, use [Vercel Cron](https://vercel.com/docs/cron-jobs) for schedules, and use [Workflow](https://vercel.com/docs/workflows) for durable multi-step work. Keep request handlers idempotent.

## Next steps

- Learn how [Vercel Services](https://vercel.com/docs/services) routes traffic between backends and frontends
  
- Learn how to run [Docker on Vercel](https://vercel.com/kb/guide/docker)
  
- Review the [vercel.json reference](https://vercel.com/docs/project-configuration/vercel-json)
  
- Read the [container images documentation](https://vercel.com/docs/functions/container-images) for execution limits
  
- Explore the [Vercel Frameworks documentation](https://vercel.com/docs/frameworks)