Skip to content

nao1215/truss

truss

Build CLI Integration API Integration Crates.io Crates.io Downloads License: MIT Rust

logo

truss is an image transformation tool with a shared Rust core for a CLI, an HTTP server, and a browser/WASM build.

Use the CLI for local files and shell pipelines, run the server behind a CDN or reverse proxy, process files in the browser with @nao1215/truss-wasm, or sign public URLs from Node.js with @nao1215/truss-url-signer.

Try the WASM demo in your browser - no install, no upload, runs 100 % client-side.

WASM demo screenshot

Start Here

If you want to... Start with Read next
Convert local files from the shell brew install nao1215/tap/truss or cargo install truss-image, then truss photo.png -o photo.jpg CLI, Commands
Run an HTTP image server TRUSS_BEARER_TOKEN=changeme truss serve --bind 0.0.0.0:8080 --storage-root ./images HTTP Server, API Reference, Deployment Guide
Process files in a browser app npm install @nao1215/truss-wasm WASM, WASM Integration
Generate signed public URLs from Node.js npm install @nao1215/truss-url-signer TypeScript URL Signing, Signed URL Specification

Why truss?

  • One Rust core across the CLI, the HTTP server, and the browser/WASM build.
  • Signed URLs, SSRF protections, and SVG sanitization are built in.
  • Supports JPEG, PNG, WebP, AVIF, BMP, TIFF, and SVG.
  • Runs on Linux, macOS, and Windows.
  • CLI behavior is covered by ShellSpec, and the HTTP API by runn.

Installation

CLI

Homebrew

brew install nao1215/tap/truss

Cargo

cargo install truss-image

Need S3, GCS, or Azure storage backend support at install time? See the Deployment Guide.

Prebuilt binaries are available on the GitHub Releases page. See Deployment Guide for all targets and Docker images.

JavaScript Packages

Install only the package you need:

# Browser/WASM package
npm install @nao1215/truss-wasm

# Node.js URL signer
npm install @nao1215/truss-url-signer

Package source and package-specific READMEs live in packages/truss-wasm and packages/truss-url-signer.

Container Image

docker pull ghcr.io/nao1215/truss:latest

Quick Start

CLI

The convert subcommand can be omitted: truss photo.png -o photo.jpg is equivalent to truss convert photo.png -o photo.jpg. Run truss convert --help to see the full set of options.

# Convert format
truss photo.png -o photo.jpg

# Resize + convert
truss photo.png -o thumb.webp --width 800 --format webp --quality 75

# Optimize in place with the shared pipeline
truss optimize photo.jpg -o photo-optimized.jpg --mode auto

# Convert from a remote URL
truss --url https://example.com/img.png -o out.avif --format avif

# Sanitize SVG (remove scripts and external references)
truss diagram.svg -o safe.svg

# Inspect metadata
truss inspect photo.jpg

Format conversion & quality

truss supports JPEG, PNG, WebP, AVIF, BMP, TIFF, and SVG. The output format is inferred from the file extension, or you can specify it explicitly with --format.

Format File size (640 x 427) Notes
JPEG (original) 80 KB Lossy, widely supported
WebP (--quality 80) 38 KB ~52 % smaller than JPEG
AVIF (--quality 50) 17 KB ~79 % smaller than JPEG
PNG 480 KB Lossless
# JPEG -> WebP (smaller file, same visual quality)
truss photo.jpg -o photo.webp --quality 80

# JPEG -> AVIF (best compression)
truss photo.jpg -o photo.avif --quality 50

# Explicit format override (ignore extension)
truss photo.jpg -o output.bin --format png

Use --quality <1-100> to control lossy encoding. Lower values produce smaller files at the cost of visual quality.

Use --optimize auto|lossless|lossy on truss convert, or the dedicated truss optimize subcommand, to reduce output size with format-aware encoding choices. Add --target-quality ssim:0.98 or --target-quality psnr:42 when you want lossy optimization to aim for a specific perceptual threshold.

Quality 90 (95 KB) Original (80 KB) Quality 30 (27 KB)
q90 original q30

Resize & fit modes

Specify --width and/or --height to resize. When both are given, --fit controls how the image fits the target box:

Mode Behavior
contain (default) Scale down to fit entirely inside the box, preserving aspect ratio. Padding is filled with --background.
cover Scale to fill the box completely, cropping excess. Use --position to choose the crop anchor.
fill Stretch to exact dimensions (ignores aspect ratio).
inside Like contain, but never upscales a smaller image.
Original (640 x 427) contain 300 x 300 cover 300 x 300 fill 300 x 300 inside 300 x 300
original contain cover fill inside
# contain -- fit inside the box, pad with gray background
truss photo.jpg -o out.jpg --width 300 --height 300 --fit contain --background CCCCCC

# cover -- fill the box, crop the excess
truss photo.jpg -o out.jpg --width 300 --height 300 --fit cover

# fill -- stretch to exact dimensions
truss photo.jpg -o out.jpg --width 300 --height 300 --fit fill

# inside -- like contain, but never upscale
truss photo.jpg -o out.jpg --width 300 --height 300 --fit inside

# Width only -- height is calculated to preserve aspect ratio
truss photo.jpg -o out.jpg --width 800

Cover position

When using --fit cover, --position controls which part of the image is kept:

--position top-left --position center (default) --position bottom-right
top-left center bottom-right

Available positions: center, top, right, bottom, left, top-left, top-right, bottom-left, bottom-right.

truss photo.jpg -o thumb.jpg --width 300 --height 300 --fit cover --position top-left

Crop, rotate & background

# Crop a region (x, y, width, height) -- applied before resize
truss photo.jpg -o cropped.jpg --crop 100,50,400,300

# Rotate 270 degrees clockwise (accepts 0, 90, 180, 270)
truss photo.jpg -o rotated.jpg --rotate 270

# Background color as RRGGBB or RRGGBBAA hex (useful with contain or PNG alpha)
truss photo.jpg -o out.png --width 300 --height 300 --fit contain --background FF6B35FF
Original Crop (--crop 100,50,400,300) Rotate (--rotate 270) Background (--background FF6B35FF)
original cropped rotated background

Blur, sharpen & watermark

Original Gaussian Blur (--blur 5.0) Sharpen (--sharpen 3.0) Watermark
original blurred sharpened watermarked
# Gaussian blur (sigma 0.1 - 100.0)
truss photo.jpg -o blurred.jpg --blur 5.0

# Sharpen (sigma 0.1 - 100.0)
truss photo.jpg -o sharpened.jpg --sharpen 3.0

# Watermark with full control
truss photo.jpg -o watermarked.jpg \
  --watermark logo.png \
  --watermark-position bottom-right \
  --watermark-opacity 50 \
  --watermark-margin 10

Watermark positions are the same as cover positions: center, top, right, bottom, left, top-left, top-right, bottom-left, bottom-right.

Note: --blur, --sharpen, and --watermark are raster-only and not supported for SVG inputs.

Metadata control

By default, truss strips all metadata for smaller and safer output.

Flag Behavior
--strip-metadata (default) Remove all EXIF, ICC, and other metadata
--keep-metadata Preserve EXIF, ICC, and all other supported metadata
--preserve-exif Keep EXIF only, strip ICC and others
--auto-orient (default) Apply EXIF orientation tag and reset it
--no-auto-orient Skip EXIF orientation correction
# Keep all metadata (useful for archival)
truss photo.jpg -o out.jpg --keep-metadata

# Keep EXIF only (strip ICC profiles)
truss photo.jpg -o out.jpg --preserve-exif

# Disable auto-orientation
truss photo.jpg -o out.jpg --no-auto-orient

Stdin / stdout piping

Use - for input and/or output to integrate truss into shell pipelines. When reading from stdin, --format is required for output.

# Pipe from stdin to stdout
cat photo.png | truss - -o - --format jpeg > photo.jpg

# Download, convert, and upload in one pipeline
curl -s https://example.com/img.png | truss - -o - --format webp --width 800 | \
  aws s3 cp - s3://bucket/thumb.webp

# Optimize after converting
truss photo.jpg -o - --format webp --optimize auto | cat > optimized.webp

SVG handling

truss sanitizes SVG files by removing scripts and external references, making them safe for user-generated content.

# Sanitize SVG (remove scripts, external refs)
truss diagram.svg -o safe.svg

# Rasterize SVG to PNG at a specific width
truss diagram.svg -o diagram.png --width 1024

Filenames starting with -

Use --output= (or --output) to avoid ambiguity with filenames that start with a dash:

# Dash-prefixed output: use --output= to assign the value unambiguously
truss convert input.png --output=-output.jpg

# Dash-prefixed input: use a path prefix
truss convert ./-input.png -o out.jpg

HTTP Server

Start the server from an installed binary or a local build:

TRUSS_BEARER_TOKEN=changeme truss serve --bind 0.0.0.0:8080 --storage-root ./images

Or run the published container image:

docker run -p 8080:8080 \
  -e TRUSS_BIND_ADDR=0.0.0.0:8080 \
  -e TRUSS_BEARER_TOKEN=changeme \
  -e TRUSS_STORAGE_ROOT=/data \
  -v "$(pwd)/images:/data:ro" \
  ghcr.io/nao1215/truss:latest

Resize a local image to 400 px wide WebP in one request:

curl -X POST http://localhost:8080/images \
  -H "Authorization: Bearer changeme" \
  -F "file=@photo.jpg" \
  -F 'options={"format":"webp","width":400}' \
  -o thumb.webp

If you also want public signed URLs, start the server with signing keys and generate URLs with truss sign:

TRUSS_BEARER_TOKEN=changeme \
TRUSS_SIGNING_KEYS='{"mykey":"s3cret"}' \
truss serve --bind 0.0.0.0:8080 --storage-root ./images

truss sign --base-url http://localhost:8080 \
  --path photos/hero.jpg \
  --key-id mykey \
  --secret s3cret \
  --expires 1900000000 \
  --width 800 \
  --format webp

Use truss validate to check server configuration without starting the process. See the API Reference for endpoint details and the Deployment Guide for Docker, storage backends, and production setup.

TypeScript URL Signing

Node.js only. The signer uses node:crypto and should stay on the server side; do not ship the signing secret to browsers or Edge/browser runtimes.

npm install @nao1215/truss-url-signer
import { signPublicUrl } from "@nao1215/truss-url-signer";

const signedUrl = signPublicUrl({
  baseUrl: "https://images.example.com",
  source: {
    kind: "path",
    path: "hero.jpg",
  },
  transforms: {
    width: 1200,
    format: "webp",
  },
  keyId: "public-demo",
  secret: process.env.TRUSS_SIGNING_SECRET ?? "",
  expires: Math.floor(Date.now() / 1000) + 300,
});

See packages/truss-url-signer for the full API and examples for both /images/by-path and /images/by-url.

WASM

truss also ships a browser-oriented WASM adapter for local, client-side image processing. The generated package exposes a small JS-facing API over the same Rust core used by the CLI and HTTP server.

For bundler-based browser apps, the repository includes the source for the official npm package in packages/truss-wasm. It uses the fixed feature set wasm,svg,avif, so AVIF is available and WebP stays lossless in the package build.

npm install @nao1215/truss-wasm

If you want a minimal consumer you can run immediately, see examples/vite-truss-wasm.

import {
  getCapabilitiesJson,
  inspectImageJson,
  transformImage,
} from "@nao1215/truss-wasm";

const inputBytes = new Uint8Array(await file.arrayBuffer());
const capabilities = JSON.parse(getCapabilitiesJson());
const inspected = JSON.parse(inspectImageJson(inputBytes, undefined));

const result = transformImage(
  inputBytes,
  undefined,
  JSON.stringify({
    format: "jpeg",
    width: 1200,
    quality: 80,
  }),
);

The official npm package wraps the raw browser bindings and initializes the Wasm module at import time, so consumer code does not call init() explicitly.

For maintainers:

  • node ./scripts/run-wasm-consumer-smoke.mjs packs the local npm artifact, installs it into a throwaway consumer, and runs one real transform through the published JS surface.
  • node ./scripts/run-wasm-vite-example-smoke.mjs rewires the Vite example to the local tarball and verifies that a real bundler build still succeeds.
  • node ./scripts/run-wasm-vite-example-runtime-smoke.mjs verifies the checked-in Vite example and confirms the browser runtime path in headless Chrome.

The example below assumes your page is served from a directory that also contains pkg/truss.js. When using ./scripts/build-wasm-demo.sh, that means web/dist/index.html importing ./pkg/truss.js.

import init, {
  getCapabilitiesJson,
  inspectImageJson,
  transformImage,
} from "./pkg/truss.js";

await init();

const inputBytes = new Uint8Array(await file.arrayBuffer());
const capabilities = JSON.parse(getCapabilitiesJson());
const inspected = JSON.parse(inspectImageJson(inputBytes, undefined));

const result = transformImage(
  inputBytes,
  undefined,
  JSON.stringify({
    format: "jpeg",
    width: 1200,
    quality: 80,
  }),
);

const response = JSON.parse(result.responseJson);
const outputBlob = new Blob([result.bytes], {
  type: response.artifact.mimeType,
});

The GitHub Pages demo is intentionally built with wasm,svg. The official npm package uses wasm,svg,avif. Check capabilities at runtime and see WASM Integration for package and raw-build usage, feature differences, import-path assumptions, API shapes, constraints, limits, and error handling.

Commands

Command Description
convert Convert and transform an image file (can be omitted; see above)
optimize Optimize an image with format-aware auto/lossless/lossy modes (truss optimize photo.jpg -o photo-optimized.jpg --mode auto)
inspect Show metadata (format, dimensions, alpha) of an image
serve Start the HTTP image-transform server (implied when server flags are used at the top level)
validate Validate server configuration without starting the server (useful in CI/CD)
sign Generate a signed public URL for the server
completions Generate shell completion scripts
help Show help for a command (for example truss help convert)

Top-level shortcuts:

  • truss <INPUT> -o <OUTPUT> [OPTIONS] is implicit convert
  • truss --bind <ADDR> [OPTIONS] is implicit serve
  • truss --version prints version information

Documentation

Page Description
Configuration Reference Environment variables, storage backends, logging, and all server settings
API Reference HTTP endpoints, request/response formats, CDN integration
Signed URL Specification Canonicalization rules, compatibility policy, and SDK guidance for public signed URLs
Deployment Guide Docker, prebuilt binaries, cloud storage (S3/GCS/Azure), production setup
Development Guide Building from source, testing, benchmarks, WASM demo, contributing
WASM Integration Browser build flags, JS API contract, runtime capabilities, limits, and caveats
Prometheus Metrics Metrics reference, bucket boundaries, example PromQL queries
Pipeline Order Transform stage order and SVG-specific constraints
OpenAPI Spec Machine-readable API specification

Architecture

flowchart TB
    CLI["CLI<br/>(truss convert)"] --> Core
    Server["HTTP Server<br/>(truss serve)"] --> Core
    WASM["WASM<br/>(browser)"] --> Core

    subgraph Core["Shared Rust core"]
        direction LR
        Sniff["Detect format"] --> Transform["Crop / resize / blur / sharpen / watermark"]
        Transform --> Encode["Encode output"]
    end

    Server --> Storage

    subgraph Storage["Storage backends"]
        FS["Local filesystem"]
        S3["S3"]
        GCS["GCS"]
        Azure["Azure Blob"]
    end
Loading

CLI reads local files or fetches remote URLs directly. The HTTP server resolves images from storage backends or client uploads. The WASM build processes files selected in the browser.

Performance

Benchmarks are defined in benches/transform.rs. The numbers below were measured with criterion on a single core using the sample image in this repository. Use them as a local reference and run just bench on your hardware to compare.

Operation Time
JPEG -> PNG 8.2 us
JPEG -> WebP (q 80) 37 us
JPEG -> AVIF (q 80) 242 us
Resize 100 x 100 (cover) 317 us
Resize 800 x 600 (cover) 11.4 ms
Resize 1920 x 1080 (cover) 68 ms
Blur (sigma 5.0) 6.8 us
Sharpen (sigma 3.0) 5.9 us
SVG sanitize (passthrough) 386 ns
SVG -> PNG 1024 w rasterize 649 us
Format detection (sniff) 18 ns

Comparison

Feature comparison with imgproxy and imagor as of March 2026. Check upstream documentation if you need to confirm a specific feature before migrating or standardizing on a tool.

Feature truss imgproxy imagor
Language Rust Go Go
Runtime dependencies None libvips (C) libvips (C)
CLI Yes No No
WASM browser demo Yes No No
Signed URLs Yes Yes Yes
JPEG / PNG / WebP / AVIF Yes Yes Yes
JPEG XL (JXL) No Input only Yes
TIFF Yes Yes Yes
GIF animation processing No (out of scope) Yes Yes
SVG sanitization Yes Yes No
Smart crop No Yes Yes
Sharpen filter Yes Yes Yes
Crop / Trim / Padding Yes Yes Yes
S3 Yes Yes Yes
GCS Yes Yes Yes
Azure Blob Storage Yes Yes No
Watermark Yes Yes Yes
Prometheus metrics Yes Yes Yes
License MIT Apache 2.0 Apache 2.0

Roadmap

See the public roadmap for planned features and milestones.

Contributing

Contributions are welcome. See CONTRIBUTING.md for details.

  • Look for good first issue to get started.
  • Report bugs and request features via Issues.
  • If the project is useful, starring the repository helps.
  • Support via GitHub Sponsors is also welcome.
  • Sharing the project on social media or in blog posts is appreciated.

License

Released under the MIT License.

About

Rust image toolkit for CLI, HTTP, and WASM with signed URLs, SSRF protection, and AVIF/WebP/SVG support.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors