Post

Rust Build and Deployment Tools Guide

A comprehensive guide to Rust build tooling, compilation targets, packaging with crates.io, and deployment patterns including static binaries, Docker, and serverless.

Rust Build and Deployment Tools Guide

Introduction

Rust has emerged as a premier language for systems programming, web backends, and cloud-native applications. Its memory safety guarantees without garbage collection, combined with performance comparable to C/C++, make it an attractive choice for developers across domains.

This guide covers the complete toolchain for building, packaging, and deploying Rust applications—from basic compilation to production-ready deployments across multiple platforms.

Build Tooling: cargo and rustup

Understanding cargo

Cargo is Rust’s package manager and build system rolled into one. It handles:

  • Dependency management via Cargo.toml configuration
  • Build automation with cargo build, cargo test, and cargo run
  • Package publishing to crates.io
  • Project scaffolding with cargo new and cargo init

rustup: The Rust installer and version manager

rustup manages multiple Rust toolchains and ensures you have the right compiler (rustc) and standard library for your target platform.

1
2
3
4
5
6
7
8
9
10
11
# Install Rust (includes cargo and rustc)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# View installed toolchains
rustup show

# Add a new toolchain
rustup toolchain install nightly

# Switch between versions
rustup default stable

Basic cargo.toml structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"
description = "A sample Rust application"
license = "MIT"

[dependencies]
# Production dependencies
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

[dev-dependencies]
# Development-only dependencies
criterion = "0.5"
mockall = "0.12"

[profile.release]
opt-level = 3
lto = true

Compilation Targets and Cross-Compilation

Target triples in Rust

Rust uses target triples to identify platforms: arch-vendor-os-abi

Common targets:

  • x86_64-unknown-linux-gnu (Linux x86_64)
  • aarch64-apple-darwin (macOS Apple Silicon)
  • x86_64-pc-windows-msvc (Windows)
  • wasm32-wasi (WebAssembly)

Listing available targets

1
2
3
4
5
# List installed target toolchains
rustup target list --installed

# View all available targets
rustup target list

Installing a target

1
2
3
4
5
# Install Linux ARM64 target
rustup target add aarch64-unknown-linux-gnu

# Install WebAssembly target
rustup target add wasm32-wasi

Cross-compilation examples

Building for ARM on x86_64 Linux

1
2
3
4
5
6
7
8
# Install the target
rustup target add aarch64-unknown-linux-gnu

# Build with explicit target
cargo build --target aarch64-unknown-linux-gnu

# Or use cargo's default target (set via RUSTUP_TARGET or .cargo/config.toml)
cargo build

Cross-compiling for Windows

1
2
3
4
5
# Add the Windows target
rustup target add x86_64-pc-windows-msvc

# Build the application
cargo build --target x86_64-pc-windows-msvc --release

Target configuration file

Create .cargo/config.toml in your project to set a default target:

1
2
3
4
5
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

[build]
target = "aarch64-unknown-linux-gnu"

Packaging: crates.io and cargo publish

Creating a crate

1
2
3
4
5
# Create a new library crate
cargo new my-library --lib

# Create a new binary crate
cargo new my-app

Package metadata in Cargo.toml

1
2
3
4
5
6
7
8
9
10
11
12
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "A brief description of your crate"
documentation = "https://docs.rs/my-crate"
repository = "https://github.com/username/my-crate"
readme = "README.md"
license = "MIT OR Apache-2.0"
keywords = ["crates", "rust", "example"]
categories = ["development-tools"]

Publishing to crates.io

1
2
3
4
5
6
7
8
# Login to crates.io (requires account)
cargo login

# Verify your package metadata
cargo publish --dry-run

# Publish the crate
cargo publish

Version management

Cargo uses Semantic Versioning (SemVer):

  • 0.x.y - Initial development (breaking changes allowed)
  • x.y.z where x > 0 - Stable releases (backward compatibility required)
1
2
3
4
[dependencies]
my-crate = "1.0"        # ^1.0.0: >= 1.0.0, < 2.0.0
other-crate = "~1.4.5"  # >= 1.4.5, < 1.5.0
third-crate = "=1.2.3"  # exactly 1.2.3

Deployment Patterns

Static Binaries for Portability

Rust’s ability to produce statically linked binaries makes deployment simple—just copy the binary to any compatible system.

Producing a static binary

1
2
3
4
5
[profile.release]
opt-level = "z"     # Optimize for size
lto = true          # Link-time optimization
panic = "abort"     # Remove unwinding machinery
codegen-units = 1   # Better optimization across crates

Build with musl for static linking on Linux:

1
2
3
4
5
# Install musl target
rustup target add x86_64-unknown-linux-musl

# Build static binary
cargo build --target x86_64-unknown-linux-musl --release

The resulting binary has no external dependencies and can run on any Linux system with the same architecture.

Docker-based static compilation

Use a multi-stage Dockerfile to build static binaries:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM rust:1-alpine AS builder

# Install musl tools
RUN apk add --no-cache musl-dev

WORKDIR /build
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs
RUN cargo build --release --target x86_64-unknown-linux-musl

# Copy source and rebuild properly
COPY src ./src
RUN cargo build --release --target x86_64-unknown-linux-musl

FROM scratch
COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/my-app /my-app
ENTRYPOINT ["/my-app"]

Containerized Deployments with Docker

Rust applications work well in containers, especially for complex deployments.

Standard Rust Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM rust:1-slim AS builder

WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN cargo fetch

COPY src ./src
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=builder /app/target/release/my-app .

CMD ["./my-app"]

Multi-stage optimized Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Stage 1: Build
FROM rust:1-slim AS builder
WORKDIR /build
COPY Cargo.toml Cargo.lock ./
RUN cargo fetch
COPY src ./src
RUN cargo build --release --locked

# Stage 2: Runtime
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y \
    libssl3 \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY --from=builder /build/target/release/my-app .

CMD ["./my-app"]

Serverless Deployments

Rust is excellent for serverless functions due to fast startup times and low memory usage.

AWS Lambda with Rust

  1. Install the cargo-lambda tool:
1
cargo install cargo-lambda cargo-lambda-builders
  1. Create a new Lambda function:
1
cargo lambda new my-function --runtime rust
  1. Build for AWS Lambda:
1
cargo lambda build --release --output-format zip
  1. Deploy:
1
cargo lambda deploy my-function

Example Lambda handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use lambda_runtime::{lambda, error::HandlerError, context::Context};
use serde_json::Value;

async fn my_handler(
    event: Value,
    _context: Context,
) -> Result<Value, HandlerError> {
    Ok(serde_json::json!({
        "message": "Hello from Rust Lambda!",
        "input": event
    }))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    lambda!(my_handler);
    Ok(())
}

Cloudflare Workers

1
2
3
4
5
6
7
8
9
# Install Wrangler CLI
cargo install wrangler

# Create a new worker
wrangler generate my-worker
cd my-worker

# Build for WebAssembly target
cargo build --target wasm32-wasi --release

Decision Guidance Based on Platform Constraints

Deployment ScenarioRecommended ApproachRationale
Embedded systemsStatic binary with muslNo dynamic library dependencies, minimal size
Linux containersStandard Docker image with slim baseSmaller images, faster pull times
Windows deploymentNative Windows build + installerBest integration with Windows ecosystem
Cross-platform desktopMultiple static binaries per targetSingle file distribution, no runtime setup
AWS Lambdacargo-lambda + musl targetOptimized for cold start and size limits
Azure FunctionsStandard Rust compilation + Azure CLINative support for .NET-hosted Rust WASM
KubernetesMulti-stage Docker buildReproducible builds, container orchestration

Size optimization tips

1
2
3
4
5
6
[profile.release]
opt-level = "z"       # Optimize for size
lto = true           # Link-time optimization
panic = "abort"      # Remove unwinding stack traces
codegen-units = 1    # Single codegen unit for better optimizations
strip = true         # Strip debug symbols (use strip command after build)

After building, manually strip the binary:

1
2
cargo build --release
strip target/release/my-app

Memory-constrained environments

For embedded or low-memory deployments:

  1. Use panic = "abort" to eliminate unwinding code
  2. Disable default features when possible:
    1
    2
    3
    4
    
    [dependencies.tokio]
    version = "1.0"
    default-features = false
    features = ["rt", "macros"]
    
  3. Use --release --no-default-features when appropriate

CI/CD Integration

GitHub Actions example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
name: Rust CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        
      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            target/
          key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-rust-
            
      - name: Build
        run: cargo build --verbose
        
      - name: Run tests
        run: cargo test --verbose

  cross-compile:
    needs: build-and-test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target: [x86_64-unknown-linux-musl, aarch64-unknown-linux-gnu]
    steps:
      - uses: actions/checkout@v4
      
      - name: Install target
        run: rustup target add ${{ matrix.target }}
        
      - name: Build release binary
        run: cargo build --target ${{ matrix.target }} --release
        
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: my-app-${{ matrix.target }}
          path: target/${{ matrix.target }}/release/my-app

  publish-crates:
    needs: cross-compile
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v4
      
      - name: Publish to crates.io
        run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }}

GitLab CI example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
stages:
  - build
  - test
  - deploy

variables:
  CARGO_INCREMENTAL: "0"
  CARGO_NET_RETRY: "10"
  RUSTUP_MAX_RETRIES: "10"

build:
  stage: build
  image: rust:1-slim
  script:
    - cargo fetch
    - cargo build --release
  artifacts:
    paths:
      - target/release/my-app

test:
  stage: test
  image: rust:1-slim
  script:
    - cargo test
  coverage: /cov: \d+%/

deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache curl
  script:
    - curl -X POST $DEPLOY_WEBHOOK -d 'status=running'
    - ./target/release/my-app &
    - sleep 5
    - curl -f http://localhost:$PORT/health || exit 1
  environment:
    name: production

Conclusion

Rust’s toolchain provides powerful capabilities for building and deploying applications across diverse platforms. By understanding cargo, rustup, and the various deployment patterns, you can create efficient, portable, and maintainable Rust applications.

Key takeaways:

  1. Build tooling: Use cargo for all build-related tasks and rustup for managing toolchains
  2. Cross-compilation: Install targets with rustup target add and build with --target
  3. Packaging: Follow SemVer and publish to crates.io for library distribution
  4. Deployment patterns: Choose based on your platform—static binaries for portability, containers for consistency, serverless for scalability

What deployment scenario are you building for? Share your experiences in the comments!

This post is licensed under CC BY 4.0 by the author.