truss

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.

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
Cargo
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
# Node.js URL signer
Package source and package-specific READMEs live in packages/truss-wasm and packages/truss-url-signer.
Container Image
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
# Resize + convert
# Optimize in place with the shared pipeline
# Convert from a remote URL
# Sanitize SVG (remove scripts and external references)
# Inspect metadata
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)
# JPEG -> AVIF (best compression)
# Explicit format override (ignore extension)
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) |
|---|---|---|
![]() |
![]() |
![]() |
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 |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
# contain -- fit inside the box, pad with gray background
# cover -- fill the box, crop the excess
# fill -- stretch to exact dimensions
# inside -- like contain, but never upscale
# Width only -- height is calculated to preserve aspect ratio
Cover position
When using --fit cover, --position controls which part of the image is kept:
--position top-left |
--position center (default) |
--position bottom-right |
|---|---|---|
![]() |
![]() |
![]() |
Available positions: center, top, right, bottom, left, top-left, top-right, bottom-left, bottom-right.
Crop, rotate & background
# Crop a region (x, y, width, height) -- applied before resize
# Rotate 270 degrees clockwise (accepts 0, 90, 180, 270)
# Background color as RRGGBB or RRGGBBAA hex (useful with contain or PNG alpha)
| Original | Crop (--crop 100,50,400,300) |
Rotate (--rotate 270) |
Background (--background FF6B35FF) |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Blur, sharpen & watermark
| Original | Gaussian Blur (--blur 5.0) |
Sharpen (--sharpen 3.0) |
Watermark |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
# Gaussian blur (sigma 0.1 - 100.0)
# Sharpen (sigma 0.1 - 100.0)
# Watermark with full control
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--watermarkare 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)
# Keep EXIF only (strip ICC profiles)
# Disable auto-orientation
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
|
# Download, convert, and upload in one pipeline
| | \
# Optimize after converting
|
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)
# Rasterize SVG to PNG at a specific width
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
# Dash-prefixed input: use a path prefix
HTTP Server
Start the server from an installed binary or a local build:
TRUSS_BEARER_TOKEN=changeme
Or run the published container image:
Resize a local image to 400 px wide WebP in one request:
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"}' \
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.
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.
If you want a minimal consumer you can run immediately, see examples/vite-truss-wasm.
import from "@nao1215/truss-wasm";
const inputBytes = ;
const capabilities = JSON.;
const inspected = JSON.;
const result = ;
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.mjspacks 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.mjsrewires the Vite example to the local tarball and verifies that a real bundler build still succeeds.node ./scripts/run-wasm-vite-example-runtime-smoke.mjsverifies 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 from "./pkg/truss.js";
await ;
const inputBytes = ;
const capabilities = JSON.;
const inspected = JSON.;
const result = ;
const response = JSON.;
const outputBlob = ;
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 implicitconverttruss --bind <ADDR> [OPTIONS]is implicitservetruss --versionprints 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
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 issueto 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.














