workspace-cache 0.1.1

A CLI to generate cached Docker builds for Rust workspaces
workspace-cache-0.1.1 is not a library.

workspace-cache

Crate IO Crates.io Downloads Continuous Integration Deps Status License

Like cargo-chef but focused on Rust workspaces with multiple binaries (microservices). Generates optimized Dockerfiles with proper layer caching.

Installation

cargo install workspace-cache

Or install from git for the latest development version:

cargo install --git https://github.com/preiter93/workspace-cache workspace-cache

Quick Start

Generate a Dockerfile for your service:

workspace-cache dockerfile --bin user -o Dockerfile

This produces an optimized multi-stage Dockerfile:

# Stage 1: Install workspace-cache tool
FROM rust:1.94-bookworm AS base
WORKDIR /app
RUN cargo install workspace-cache@0.1.1

# Stage 2: Generate minimal workspace with stub sources
FROM base AS planner
COPY . .
RUN workspace-cache deps --bin user

# Stage 3: Build dependencies only (cached until Cargo.toml/Cargo.lock change)
FROM base AS deps
COPY --from=planner /app/.workspace-cache .
RUN cargo build --release

# Stage 4: Build the actual binary with real source code
FROM deps AS builder
RUN rm -rf crates/user crates/common
COPY crates/user crates/user
COPY crates/common crates/common
RUN cargo clean --release -p user -p common
RUN cargo build --release --bin user

# Stage 5: Minimal runtime image
FROM debian:bookworm-slim AS runtime
COPY --from=builder /app/target/release/user /usr/local/bin/user
ENTRYPOINT ["/usr/local/bin/user"]

Build & Run

docker build -f Dockerfile -t user .
docker run --rm user

How It Works

  1. planner - Creates a minimal workspace with stub sources for dependency resolution
  2. deps - Builds dependencies (cached until Cargo.toml/Cargo.lock changes)
  3. builder - Copies real source and builds binary
  4. runtime - Minimal image with just the binary

When source files change but dependencies don't, Docker skips the deps stage entirely.

Workspace Optimization

workspace-cache analyzes your workspace dependency graph and includes only the necessary members for your binary. This enables isolated builds where changes to unrelated workspace members don't invalidate your cache. For example, if you have user, order, and payment services, changing the payment service won't trigger rebuilds of user or order containers.

This optimization applies to both the dependency build stage and the binary build stage. The generated Dockerfile only copies workspace members that your binary depends on, ensuring that changes to independent members don't invalidate Docker's layer cache at any stage of the build.

Comparison with cargo-chef

Feature cargo-chef workspace-cache
Cached dependencies
Workspace optimization (dependencies)
Fast dependency build ✅ (--fast)
Workspace optimization (binary) ✅ (Optimization)
Dockerfile generation ✅ (Dockerfile)
GitHub Actions ✅ (CI Usage)
Get dependent workspace members ✅ (members)

Usage

The main command is dockerfile. It generates an optimized Dockerfile for your binary:

workspace-cache dockerfile --bin <binary> [OPTIONS]

Options:

  • --bin <binary> - Binary to build (required)
  • --profile <profile> - Build profile: release or debug (default: release)
  • -o, --output <path> - Output path (default: stdout)
  • --base-image <image> - Base image for build stages (default: rust:1.94-bookworm)
  • --runtime-image <image> - Runtime image (default: debian:bookworm-slim)
  • --tool-version <version> - Version of workspace-cache to install (default: current version)
  • --from-git - Install workspace-cache from git instead of crates.io
  • --fast - Fast mode: skip dependency resolution for faster generation

Examples:

# Generate release Dockerfile (default)
workspace-cache dockerfile --bin user -o Dockerfile

# Generate debug Dockerfile
workspace-cache dockerfile --bin user --profile debug -o Dockerfile.debug

# Use a specific version
workspace-cache dockerfile --bin user --tool-version 0.1.0 -o Dockerfile

# Install from git (latest dev version)
workspace-cache dockerfile --bin user --from-git -o Dockerfile

# Custom base image
workspace-cache dockerfile --bin user --base-image rust:1.80-alpine -o Dockerfile

Fast Mode

Use --fast to skip dependency resolution. This results in a less optimized cache, but speeds up Docker builds as long as no dependencies have changed.

workspace-cache dockerfile --bin user --fast -o Dockerfile

CI Usage (without Docker)

GitHub Action (Recommended)

The simplest way to use workspace-cache in CI is with the provided composite actions:

- name: Install workspace-cache
  uses: preiter93/workspace-cache/.github/actions/install-workspace-cache@main

- name: Build
  uses: preiter93/workspace-cache/.github/actions/build-workspace@main
  with:
    binary: user
    working-directory: services

- name: Run tests
  working-directory: services/.workspace-cache
  run: cargo test -p user --verbose

- name: Clippy
  working-directory: services/.workspace-cache
  run: cargo clippy -p user -- -D warnings

Important: Run tests and other cargo commands from services/.workspace-cache where the complete workspace is built.

Note: The build action compiles tests by default (build-tests: true), so dev-dependencies are cached and test compilation is fast.

See the action READMEs for more options:

Complete Example with Matrix Strategy

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        service: [user, order, payment]
    steps:
      - uses: actions/checkout@v6
      
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      
      - name: Install workspace-cache
        uses: preiter93/workspace-cache/.github/actions/install-workspace-cache@main
      
      - name: Build
        uses: preiter93/workspace-cache/.github/actions/build-workspace@main
        with:
          binary: ${{ matrix.service }}
          working-directory: services
      
      - name: Run tests
        working-directory: services/.workspace-cache
        run: cargo test -p ${{ matrix.service }} --verbose
      
      - name: Clippy
        working-directory: services/.workspace-cache
        run: cargo clippy -p ${{ matrix.service }} -- -D warnings

Manual Setup

You can also set up workspace-cache manually for more control:

- name: Install workspace-cache
  run: cargo install workspace-cache

- name: Generate minimal workspace
  run: workspace-cache deps --bin user

- name: Get cache key for dependencies
  id: cache-key
  run: |
    HASH="${{ hashFiles('.workspace-cache/Cargo.lock') }}"
    echo "key=${{ runner.os }}-workspace-cache-deps-${HASH}" >> $GITHUB_OUTPUT

- name: Cache dependencies
  uses: actions/cache@v5
  with:
    path: .workspace-cache/target
    key: ${{ steps.cache-key.outputs.key }}
    restore-keys: |
      ${{ runner.os }}-workspace-cache-deps-

- name: Build dependencies (cached when Cargo.lock unchanged)
  run: cargo build --release
  working-directory: .workspace-cache

- name: Copy real sources
  run: |
    workspace-cache members --bin user | while read path name; do
      rm -rf .workspace-cache/$path
      cp -r $path .workspace-cache/$path
    done

- name: Build binary
  run: |
    PACKAGES=$(workspace-cache members --bin user | awk '{print "-p " $2}' | tr '\n' ' ')
    cargo clean --release $PACKAGES
    cargo build --release --bin user
  working-directory: .workspace-cache

The cache key is based on the generated .workspace-cache/Cargo.lock, so dependencies are only rebuilt when they change. On cache hits, the dependency build step completes in seconds.

CLI Reference

deps - Generate minimal workspace

workspace-cache deps --bin <binary> [--output <dir>] [--fast]

Generates a minimal workspace with stub sources for the specified binary.

members - List workspace members

workspace-cache members --bin <binary>

Lists workspace members that the binary depends on (used internally by CI actions).

dockerfile - Generate Dockerfile

workspace-cache dockerfile --bin <binary> [OPTIONS]

See Usage section above for details.

Testing

Run unit tests:

cargo test

Test locally in your workspace:

# Generate minimal workspace for a binary
workspace-cache deps --bin user

# Build dependencies
cd .workspace-cache
cargo build --release

# Copy real sources and build (deps are cached)
rm -rf crates/user crates/common
cp -r ../crates/user crates/user
cp -r ../crates/common crates/common
cargo build --release --bin user

Note: This mirrors how the generated Dockerfile works. The key is building the final binary from within .workspace-cache/ after copying real sources.

Example

See rust-microservices for a complete example using workspace-cache in a real microservices project with Docker and CI/CD.

License

MIT