{"slug":"how-to-deploy-rust-axum-to-a-vps","url":"https://tako.sh/blog/how-to-deploy-rust-axum-to-a-vps/","canonical":"https://tako.sh/blog/how-to-deploy-rust-axum-to-a-vps/","title":"How to Deploy a Rust Axum App to a VPS with Automatic HTTPS","date":"2026-11-02T03:15","description":"Deploy a Rust Axum app to a VPS with Tako's SDK, a multi-stage Dockerfile, automatic HTTPS, health checks, and rolling updates.","author":null,"image":"58ad61cea9a8","imageAlt":"A coral octopus secures an Axum application module inside a VPS server as an HTTPS lock lights up.","headings":[{"depth":2,"slug":"build-an-axum-app-with-the-tako-rust-sdk","text":"Build an Axum app with the Tako Rust SDK"},{"depth":2,"slug":"package-the-rust-binary-in-a-container-release","text":"Package the Rust binary in a container release"},{"depth":2,"slug":"deploy-to-the-vps-and-verify-https","text":"Deploy to the VPS and verify HTTPS"}],"markdown":"> **Networking update.** The server setup described here uses Tako’s legacy VPN integration. For new installations, see [native WireGuard networks](/blog/native-wireguard-networks-for-vps-apps/). For an existing Tailscale or NetBird host, follow the [migration guide](/blog/migrate-tailscale-to-tako-wireguard/).\n\nYour Axum router works locally. Getting it onto a VPS adds a second project: build a Linux binary, keep it running, issue a certificate, and work out whether the next version is healthy before sending users to it.\n\nWe'll take a small Rust app through that path with Tako. The app uses the Rust SDK, a Dockerfile supplies the production build, and Tako handles public HTTPS and rolling releases. You keep your router. The certificate has somewhere else to live.\n\n## Build an Axum app with the Tako Rust SDK\n\nStart with a Rust toolchain and create a binary project:\n\n```bash\ncargo new axum-on-tako --bin\ncd axum-on-tako\n```\n\nKeep the package metadata Cargo created and add these dependencies to `Cargo.toml`:\n\n```toml\n[dependencies]\naxum = \"0.8\"\ntokio = { version = \"1\", features = [\"full\"] }\ntako = { version = \"0.2.1\", features = [\"axum\"] }\n```\n\nThe `tako` crate's `axum` feature provides the serving adapter. This example follows the [Rust and Axum guide](/docs/axum/); the implementation is in the [Rust SDK source](https://github.com/tako-sh/tako/tree/master/sdk/rust).\n\nReplace `src/main.rs` with:\n\n```rust\nuse axum::{routing::get, Router};\n\n#[tokio::main]\nasync fn main() -> std::io::Result<()> {\n    let app = Router::new()\n        .route(\"/\", get(|| async { \"Hello from Axum on Tako\" }))\n        .route(\"/api/health\", get(|| async { \"ok\" }));\n\n    tako::axum::serve(app).await\n}\n```\n\nThe routes use Axum's normal [`Router` and `get` APIs](https://github.com/tokio-rs/axum). The serving call is the integration point: `tako::axum::serve` binds the SDK listener and wraps your router with authenticated internal status handling. Keep your existing handlers and middleware on that router.\n\nOur `/api/health` endpoint is an application smoke test. Tako's internal `/status` probe has its own host and token checks, supplied by the SDK. You don't need to turn the public endpoint into an imitation of that protocol.\n\nGenerate the lockfile and check the app:\n\n```bash\ncargo generate-lockfile\ncargo check\ncargo test\n```\n\nKeep `Cargo.lock` with the application source: our production build will use `--locked`. We'll exercise the HTTP routes through the image and Tako-managed local development below. If you're adapting an existing workspace, substitute its binary name wherever this guide uses `axum-on-tako`.\n\nThe SDK also exposes runtime secrets through `tako::secret(\"NAME\")`. We'll leave this app secret-free so the first deploy only needs to prove routing and startup. When adding a database or API credentials, follow the [Secrets guide](/docs/secrets/) rather than copying values into the Dockerfile.\n\n## Package the Rust binary in a container release\n\nCreate a `Dockerfile` in the project root:\n\n```dockerfile\nFROM rust:1-bookworm AS build\nWORKDIR /src\nCOPY . .\nRUN cargo build --locked --release --bin axum-on-tako\n\nFROM debian:bookworm-slim\nRUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \\\n    && rm -rf /var/lib/apt/lists/*\nCOPY --from=build /src/target/release/axum-on-tako /app/app\nENV HOST=0.0.0.0 PORT=3000\nEXPOSE 3000\nCMD [\"/app/app\"]\n```\n\nThis follows Tako's [Rust Dockerfile example](/docs/dockerfile/). The first stage compiles the application; the second copies the binary into the runtime image. Docker's [multi-stage build guide](https://docs.docker.com/build/building/multi-stage/) explains the same separation between build tools and shipped artifacts.\n\nBoth stages use Bookworm here to keep their native-library baseline aligned. Projects that link additional system libraries must provide compatible runtime packages too. Pin the tested base-image versions or digests for production; the broad tags above keep the example readable.\n\nAdd a `.dockerignore`:\n\n```text\n.git\n.tako\n.env*\ntarget\n```\n\nTako first stages the upload context respecting `.gitignore` and its own exclusions. The image builder then applies `.dockerignore`. Ensure your Rust sources, manifests, lockfile, and Dockerfile survive both steps. A missing source file cannot be recovered by `COPY . .` after it has already been excluded from the upload.\n\nIf Docker is available locally, smoke-test the image:\n\n```bash\ndocker build -t axum-on-tako .\ndocker run --rm -p 3000:3000 axum-on-tako\n```\n\nIn another terminal, request `http://localhost:3000/` and `http://localhost:3000/api/health`; expect the greeting and `ok`. This verifies image assembly and the HTTP listener; the full Tako bootstrap and deployment checks happen on the server.\n\nYou can use Podman for that local smoke test instead. A local image builder is optional for `tako deploy`: Tako uploads the source and builds the image on the target VPS with Podman. This gives the Rust compiler a Linux build environment without requiring a separately published application image.\n\nCreate `tako.toml`:\n\n```toml\nname = \"axum-on-tako\"\ncontainer = \"Dockerfile\"\ndev = \"cargo run\"\n\n[envs.production]\nroute = \"axum.example.com\"\nservers = [\"prod\"]\n```\n\nThe Dockerfile owns the production build and startup command. Container releases reject `main`, `start`, `assets`, `[build]`, and `[[build_stages]]` in this configuration. There is no built-in Rust preset or `runtime = \"rust\"`; this tutorial uses the documented [container release path](/docs/docker/).\n\nThe explicit `dev` command runs Rust locally. After installing the CLI in the next section, run [`tako dev`](/docs/development/) from this configured project. Open the HTTPS URL it reports and request `/api/health` there too. Tako supplies the SDK bootstrap and listener settings for that process. Local development does not build or run the Dockerfile, so check the image separately when changing system packages or image assembly.\n\n## Deploy to the VPS and verify HTTPS\n\nInstall the Tako CLI on your workstation:\n\n```bash\ncurl -fsSL https://tako.sh/install.sh | sh\n```\n\nUse a supported Linux VPS with glibc, systemd, and Tako's required isolation controllers. Connect it and your workstation through Tailscale or NetBird, allowing SSH and private management traffic on TCP `9844`. The [server setup guide](/docs/servers/) covers the requirements and access policies.\n\nFor an existing NetBird network, enroll the server like this:\n\n```bash\ntako servers add ubuntu@my-server.netbird.cloud --vpn netbird --name prod\ntako servers list --json\n```\n\nReplace the hostname and administrator with your actual values. Interactive setup offers installation or repair if needed. If you're using Tailscale, select `--vpn tailscale` and its server hostname instead. The inventory name `prod` must match the name in `tako.toml`.\n\nPoint `axum.example.com` at the VPS's public address. Use your own domain, and allow public HTTP `80` and HTTPS `443`. For this direct-to-VPS setup, the default Let's Encrypt mode uses HTTP-01 validation. A route declaration does not create public DNS records; the [TLS guide](/docs/routing/) explains the certificate choices.\n\nDeploy from the project directory:\n\n```bash\ntako deploy --env production\n```\n\nThe server builds the image, starts its default command, and checks the SDK's internal status response before routing public requests to healthy replacement instances. The path looks like this:\n\n```d2\ndirection: right\nlaptop: \"Laptop\\nRust source + Dockerfile\"\nvps: \"VPS\" {\n  build: \"Podman image build\"\n  proxy: \"Tako proxy\\npublic HTTPS\" {style.fill: \"#E88783\"}\n  app: \"Axum + Tako SDK\\ncontainer port 3000\" {style.fill: \"#9BC4B6\"}\n  build -> app\n  proxy -> app: \"private loopback mapping\"\n}\nbrowser: \"Browser\"\nlaptop -> vps.build: \"source upload\"\nbrowser -> vps.proxy: \"axum.example.com\"\n```\n\nCheck the actual deployment:\n\n```bash\ntako status --json\ntako logs --env production --days 1 --json\ncurl -i https://axum.example.com/\ncurl -i https://axum.example.com/api/health\n```\n\nConfirm the certificate, greeting, and `ok` response. A healthy rollout checks startup; exercising application endpoints checks the behavior users receive. Repeat the public requests after the next deploy, especially when changing routes, dependencies, or bootstrap behavior.\n\nKeep these container boundaries in mind as the app grows:\n\n| Concern | This deployment |\n| --- | --- |\n| HTTP listener | SDK binds `0.0.0.0:3000` inside the container. |\n| Public HTTPS | Tako terminates TLS and routes traffic to healthy instances. |\n| Private values | SDK reads container bootstrap from `TAKO_BOOTSTRAP_DATA`. |\n| Persistent files | Tako mounts the app data directory at `/tako/data` by default and exposes it through `TAKO_DATA_DIR`; instances on one server share it. |\n\nThe [deployment guide](/docs/deployment/) covers rolling updates, and [Operations](/docs/operations/) covers logs and release inspection. Start with these two routes, verify one real deploy, then bring the rest of your Axum application along with the same SDK serving call and image build."}