image-proxy

Development

How to develop and run image-proxy locally

Development

Run the app locally with:

IMAGE_PROXY_ROOT_PATH=./data/images RUST_LOG=DEBUG cargo run --release
  • IMAGE_PROXY_ROOT_PATH — Must be overridden for development. Set it to any directory containing images for testing.
  • RUST_LOG=DEBUG — Enables detailed logs about request handling and transformations. Highly recommended when testing new features or troubleshooting.
  • cargo run --release — Release mode is important for realistic performance testing, especially for image transformations (debug mode is significantly slower, particularly for AVIF encoding).

Memory allocator (jemalloc)

Docker images run with jemalloc via LD_PRELOAD so post-load RSS returns to the OS after heavy concurrent transforms (decode/resize/encode). Local cargo run uses the system allocator by default; that is fine for development.

To force jemalloc locally (same approach as production), install the package (jemalloc on Arch/Debian/Alpine) and:

# Arch / most systems
export LD_PRELOAD=/usr/lib/libjemalloc.so.2
# Debian/Ubuntu multiarch (if the path above is missing):
# export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2

# Optional: return free pages ~1s after they become idle (matches the Docker image)
export MALLOC_CONF=background_thread:true,dirty_decay_ms:1000,muzzy_decay_ms:1000

IMAGE_PROXY_ROOT_PATH=./data/images RUST_LOG=DEBUG cargo run --release

LD_PRELOAD covers the whole process (Rust + native codecs such as libwebp/libjxl/dav1d). Without it, residual RSS after load tests can stay high due to multi-arena system malloc.

Benchmarking

To measure how many images per second the proxy can process on the current machine, use the built-in benchmark example (always run with --release):

cargo run --example bench --release

Example output:

image-proxy throughput benchmark
scenario: passthrough  |  duration: 5s  |  concurrency: 64
cache: disabled (raw processing)

----------------------------------------
Scenario: passthrough
Images processed: 28431
Elapsed: 5.01s
Throughput: 5674.9 images/sec
----------------------------------------

Scenarios

Pass the scenario as the first argument:

CommandDescription
cargo run --example bench --releasePass-through (fast path, no transforms)
... resize-jpegResize + JPEG re-encode
... resize-webpResize + WebP
... resize-avifResize + AVIF (slowest)
... resize-jpeg-cachedSame as above but with cache enabled + warmup

Tune with environment variables:

BENCH_DURATION=10 BENCH_CONCURRENCY=200 cargo run --example bench --release resize-jpeg

Interpreting results

  • Pass-through is very fast (mostly limited by disk reads + actix worker threads).
  • Transforms are CPU-bound in the blocking thread pool (web::block).
  • AVIF encoding is by far the most expensive; expect much lower throughput.
  • Enabling the built-in cache (the -cached scenarios) dramatically improves repeated identical transforms.
  • Numbers are localhost only. Real traffic (TLS termination, network latency, CDN, multiple instances) will be lower.
  • Always use --release.

Using external tools (alternative)

You can also benchmark against a manually started server:

IMAGE_PROXY_ROOT_PATH=./data/images cargo run --release &
# in another shell:
oha -c 128 -z 30s http://127.0.0.1:8000/204902-1.jpg

oha (Rust) or wrk work well. oha is easiest to install (cargo install oha).

On this page