Skip to content

Rust Building and Deployment

Building Rust MCP Servers for Production

This guide covers building, optimizing, and deploying Rust MCP servers with proper cross-compilation, optimization, and containerization strategies.

Build Configuration

Cargo.toml Optimization

[package]
name = "mcp-server-rust"
version = "0.1.0"
edition = "2021"

# Build profiles
[profile.release]
# Enable link-time optimization
lto = "fat"
# Use only one codegen unit for maximum optimization
codegen-units = 1
# Abort on panic instead of unwinding (smaller binary)
panic = "abort"
# Strip debug info and symbols
strip = true
# Optimize for size
opt-level = "z"

[profile.dev]
# Enable debug info for development
debug = true
# Don't optimize for faster builds
opt-level = 0
# Enable overflow checks
overflow-checks = true

# Custom profile for size-optimized release
[profile.release-size]
inherits = "release"
opt-level = "z"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = "symbols"

# Custom profile for performance-optimized release
[profile.release-perf]
inherits = "release"
opt-level = 3
lto = "fat"
codegen-units = 1

Build Scripts and Automation

#!/bin/bash
# scripts/build.sh

set -e

# Configuration
BINARY_NAME="mcp-server"
TARGET_DIR="target"
DIST_DIR="dist"

# Parse command line arguments
PROFILE="release"
TARGET=""
FEATURES=""

while [[ $# -gt 0 ]]; do
    case $1 in
        --profile)
            PROFILE="$2"
            shift 2
            ;;
        --target)
            TARGET="$2"
            shift 2
            ;;
        --features)
            FEATURES="$2"
            shift 2
            ;;
        *)
            echo "Unknown option $1"
            exit 1
            ;;
    esac
done

# Set build info
export BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
export GIT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
export GIT_TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
export VERSION=${GIT_TAG:-"dev"}

echo "Building ${BINARY_NAME} (profile: ${PROFILE}, version: ${VERSION})"

# Build command
CARGO_CMD="cargo build --profile ${PROFILE}"

if [[ -n "$TARGET" ]]; then
    CARGO_CMD="${CARGO_CMD} --target ${TARGET}"
fi

if [[ -n "$FEATURES" ]]; then
    CARGO_CMD="${CARGO_CMD} --features ${FEATURES}"
fi

# Execute build
eval $CARGO_CMD

# Create distribution directory
mkdir -p "$DIST_DIR"

# Copy binary to dist
if [[ -n "$TARGET" ]]; then
    BINARY_PATH="${TARGET_DIR}/${TARGET}/${PROFILE}/${BINARY_NAME}"
    DIST_BINARY="${DIST_DIR}/${BINARY_NAME}-${TARGET}"
else
    BINARY_PATH="${TARGET_DIR}/${PROFILE}/${BINARY_NAME}"
    DIST_BINARY="${DIST_DIR}/${BINARY_NAME}"
fi

# Add .exe extension for Windows
if [[ "$TARGET" == *"windows"* ]]; then
    BINARY_PATH="${BINARY_PATH}.exe"
    DIST_BINARY="${DIST_BINARY}.exe"
fi

cp "$BINARY_PATH" "$DIST_BINARY"

echo "Binary built: $DIST_BINARY"

# Print binary info
echo "Binary size: $(du -h $DIST_BINARY | cut -f1)"
echo "Binary info:"
file "$DIST_BINARY" || true

Cross-Compilation Configuration

# .cargo/config.toml
[target.x86_64-unknown-linux-gnu]
linker = "x86_64-linux-gnu-gcc"

[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"

[target.x86_64-apple-darwin]
linker = "x86_64-apple-darwin-clang"

# Environment variables for build
[env]
RUSTFLAGS = "-C target-cpu=native"

Multi-Target Build Script

#!/bin/bash
# scripts/build-all.sh

set -e

# Define targets
TARGETS=(
    "x86_64-unknown-linux-gnu"
    "x86_64-unknown-linux-musl" 
    "aarch64-unknown-linux-gnu"
    "x86_64-pc-windows-gnu"
    "x86_64-apple-darwin"
    "aarch64-apple-darwin"
)

BINARY_NAME="mcp-server"
FEATURES="database,metrics"

# Install targets if not already installed
echo "Installing Rust targets..."
for target in "${TARGETS[@]}"; do
    rustup target add "$target" || true
done

# Build for each target
for target in "${TARGETS[@]}"; do
    echo "Building for $target..."
    
    # Skip targets that require special setup
    if [[ "$target" == *"windows"* ]] && ! command -v x86_64-w64-mingw32-gcc &> /dev/null; then
        echo "Skipping $target (mingw-w64 not installed)"
        continue
    fi
    
    if [[ "$target" == *"apple"* ]] && [[ "$OSTYPE" != "darwin"* ]]; then
        echo "Skipping $target (requires macOS)"
        continue
    fi
    
    # Static linking for musl
    if [[ "$target" == *"musl"* ]]; then
        export RUSTFLAGS="-C target-feature=+crt-static"
    else
        export RUSTFLAGS=""
    fi
    
    cargo build \
        --profile release-size \
        --target "$target" \
        --features "$FEATURES" \
        --locked
    
    # Copy to dist directory
    mkdir -p "dist/$target"
    
    BINARY_SRC="target/$target/release-size/$BINARY_NAME"
    BINARY_DEST="dist/$target/$BINARY_NAME"
    
    if [[ "$target" == *"windows"* ]]; then
        BINARY_SRC="${BINARY_SRC}.exe"
        BINARY_DEST="${BINARY_DEST}.exe"
    fi
    
    cp "$BINARY_SRC" "$BINARY_DEST"
    
    echo "Built: $BINARY_DEST ($(du -h $BINARY_DEST | cut -f1))"
done

echo "All builds completed!"
ls -la dist/*/

Container Building

Multi-Stage Dockerfile

# Multi-stage Dockerfile for Rust MCP server
FROM rust:1.75-slim as builder

# Install build dependencies
RUN apt-get update && apt-get install -y \
    pkg-config \
    libssl-dev \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Create app user
RUN useradd -m -u 1001 appuser

WORKDIR /app

# Copy manifests
COPY Cargo.toml Cargo.lock ./

# Create dummy main to cache dependencies
RUN mkdir src && \
    echo "fn main() {}" > src/main.rs && \
    echo '' > src/lib.rs

# Build dependencies (this layer will be cached)
RUN cargo build --profile release-size --locked
RUN rm src/*.rs

# Copy source code
COPY src ./src

# Build arguments for versioning
ARG VERSION=dev
ARG BUILD_TIME
ARG GIT_COMMIT

# Build the actual application
RUN touch src/main.rs && \
    cargo build --profile release-size --locked

# Runtime stage
FROM debian:bookworm-slim

# Install runtime dependencies
RUN apt-get update && apt-get install -y \
    ca-certificates \
    libssl3 \
    && rm -rf /var/lib/apt/lists/*

# Create app user
RUN useradd -m -u 1001 appuser

# Copy the binary from builder stage
COPY --from=builder /app/target/release-size/mcp-server /usr/local/bin/mcp-server

# Set ownership
RUN chown appuser:appuser /usr/local/bin/mcp-server

# Switch to non-root user
USER appuser

# Create directories
WORKDIR /home/appuser
RUN mkdir -p logs data config

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD /usr/local/bin/mcp-server --version || exit 1

EXPOSE 8000

# Default command
CMD ["mcp-server", "--config", "/home/appuser/config/config.yaml"]

Optimized Dockerfile with Static Binary

# Static binary Dockerfile
FROM rust:1.75-alpine as builder

# Install musl development tools
RUN apk add --no-cache musl-dev pkgconfig openssl-dev openssl-libs-static

WORKDIR /app

# Copy manifests
COPY Cargo.toml Cargo.lock ./

# Copy source
COPY src ./src

# Build static binary
ENV RUSTFLAGS="-C target-feature=+crt-static"
RUN cargo build \
    --profile release-size \
    --target x86_64-unknown-linux-musl \
    --locked

# Runtime stage - using scratch for minimal image
FROM scratch

# Copy CA certificates
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# Copy the static binary
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release-size/mcp-server /mcp-server

# Create minimal filesystem
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group

# Use non-root user (create entry in builder stage if needed)
USER 1001:1001

EXPOSE 8000

ENTRYPOINT ["/mcp-server"]

Docker Compose for Development

# docker-compose.dev.yml
version: '3.8'

services:
  mcp-server:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "8000:8000"
    environment:
      - RUST_LOG=debug
      - DATABASE_URL=postgres://postgres:password@postgres:5432/mcp_dev
    volumes:
      - .:/app
      - cargo-cache:/usr/local/cargo/registry
      - target-cache:/app/target
    depends_on:
      - postgres
    command: cargo watch -x 'run --bin mcp-server'

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: mcp_dev
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  cargo-cache:
  target-cache:
  postgres_data:

Development Dockerfile

# Dockerfile.dev - for development with hot reload
FROM rust:1.75-slim

# Install development dependencies
RUN apt-get update && apt-get install -y \
    pkg-config \
    libssl-dev \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Install cargo-watch for hot reloading
RUN cargo install cargo-watch

WORKDIR /app

# Copy manifests
COPY Cargo.toml Cargo.lock ./

# Pre-build dependencies
RUN mkdir src && \
    echo "fn main() {}" > src/main.rs && \
    cargo build && \
    rm -rf src

# Expose port
EXPOSE 8000

# Default command for development
CMD ["cargo", "watch", "-x", "run"]

GitHub Actions CI/CD

Complete Build Pipeline

# .github/workflows/build.yml
name: Build and Release

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

env:
  CARGO_TERM_COLOR: always

jobs:
  test:
    name: Test Suite
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable
      with:
        components: rustfmt, clippy
    
    - name: Cache dependencies
      uses: actions/cache@v3
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
    
    - name: Check formatting
      run: cargo fmt -- --check
    
    - name: Run clippy
      run: cargo clippy -- -D warnings
    
    - name: Run tests
      run: cargo test --all-features

  build:
    name: Build Release
    needs: test
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
          - os: ubuntu-latest
            target: aarch64-unknown-linux-gnu
          - os: windows-latest
            target: x86_64-pc-windows-msvc
          - os: macos-latest
            target: x86_64-apple-darwin
          - os: macos-latest
            target: aarch64-apple-darwin

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable
      with:
        targets: ${{ matrix.target }}
    
    - name: Install cross-compilation tools
      if: matrix.target == 'aarch64-unknown-linux-gnu'
      run: |
        sudo apt-get update
        sudo apt-get install -y gcc-aarch64-linux-gnu
    
    - name: Install musl tools
      if: matrix.target == 'x86_64-unknown-linux-musl'
      run: |
        sudo apt-get update
        sudo apt-get install -y musl-tools
    
    - name: Cache dependencies
      uses: actions/cache@v3
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
    
    - name: Build binary
      run: |
        cargo build --profile release-size --target ${{ matrix.target }} --locked
    
    - name: Package binary
      shell: bash
      run: |
        VERSION=${GITHUB_REF#refs/tags/}
        if [[ "$VERSION" == "refs/heads/main" ]]; then
          VERSION="main-$(git rev-parse --short HEAD)"
        fi
        
        BINARY_NAME="mcp-server"
        if [[ "${{ matrix.target }}" == *"windows"* ]]; then
          BINARY_NAME="${BINARY_NAME}.exe"
        fi
        
        ARCHIVE_NAME="mcp-server-${VERSION}-${{ matrix.target }}"
        
        mkdir -p dist
        cp target/${{ matrix.target }}/release-size/${BINARY_NAME} dist/
        
        cd dist
        if [[ "${{ matrix.target }}" == *"windows"* ]]; then
          7z a ${ARCHIVE_NAME}.zip ${BINARY_NAME}
        else
          tar czf ${ARCHIVE_NAME}.tar.gz ${BINARY_NAME}
        fi
    
    - name: Upload artifacts
      uses: actions/upload-artifact@v3
      with:
        name: binaries-${{ matrix.target }}
        path: dist/*

  docker:
    name: Build Docker Image
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Login to DockerHub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: your-org/mcp-server-rust
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=semver,pattern={{version}}
          type=semver,pattern={{major}}.{{minor}}
    
    - name: Build and push
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        build-args: |
          VERSION=${{ steps.meta.outputs.version }}
          BUILD_TIME=${{ steps.meta.outputs.date }}
          GIT_COMMIT=${{ github.sha }}

  release:
    name: Create Release
    needs: [test, build]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/')
    
    steps:
    - name: Download artifacts
      uses: actions/download-artifact@v3
    
    - name: Create release
      uses: softprops/action-gh-release@v1
      with:
        files: |
          binaries-*/mcp-server-*
        generate_release_notes: true
        draft: false
        prerelease: false

Binary Optimization Techniques

Size Optimization Script

#!/bin/bash
# scripts/optimize.sh

set -e

BINARY_NAME="mcp-server"
BINARY_PATH="target/release-size/$BINARY_NAME"

echo "Original binary size:"
ls -lh "$BINARY_PATH"

# Strip additional symbols
echo "Stripping symbols..."
strip "$BINARY_PATH"

echo "Stripped binary size:"
ls -lh "$BINARY_PATH"

# Compress with UPX (optional)
if command -v upx &> /dev/null; then
    echo "Compressing with UPX..."
    cp "$BINARY_PATH" "${BINARY_PATH}.backup"
    upx --best "$BINARY_PATH"
    
    echo "Compressed binary size:"
    ls -lh "$BINARY_PATH"
    
    echo "Testing compressed binary..."
    if ! "$BINARY_PATH" --version; then
        echo "Compressed binary is corrupted, restoring backup"
        mv "${BINARY_PATH}.backup" "$BINARY_PATH"
    else
        rm -f "${BINARY_PATH}.backup"
        echo "Compression successful"
    fi
fi

# Analyze binary
echo "Binary analysis:"
file "$BINARY_PATH"

if command -v bloaty &> /dev/null; then
    echo "Size breakdown:"
    bloaty "$BINARY_PATH"
fi

Performance Profiling

#!/bin/bash
# scripts/profile.sh

BINARY_NAME="mcp-server"

# Build with profiling enabled
echo "Building with profiling..."
RUSTFLAGS="-C profile-generate=/tmp/pgo-data" \
    cargo build --release --target-dir target/pgo

# Run training workload
echo "Running training workload..."
./target/pgo/release/$BINARY_NAME --config config/profile.yaml &
SERVER_PID=$!

# Generate some load
sleep 2
curl http://localhost:8000/health
# Add more representative workload here

kill $SERVER_PID
wait

# Build optimized binary
echo "Building optimized binary..."
RUSTFLAGS="-C profile-use=/tmp/pgo-data -C llvm-args=-pgo-warn-missing-function" \
    cargo build --release --target-dir target/pgo-optimized

echo "PGO optimization complete"
ls -lh target/pgo-optimized/release/$BINARY_NAME

This comprehensive Rust building guide ensures your MCP server is optimized, cross-platform compatible, and ready for production deployment with minimal resource usage and maximum performance.