Pixtimize is an open source image transform API compatible with the ImageKit API. Pixtimize is compatible with any S3 bucket service.
This is a Rust rewrite built on Axum for a fast, memory-safe web server.
- Rust and Axum as the web framework and runtime
- AWS SDK for S3 to read source images and store transformed ones (any S3-compatible bucket)
- Redis to store the cached image keys
- libvips (via
libvips-rs) for fast, low-memory image decoding, resizing, and encoding
For every request the server:
- Parses the transform string (from the path or the
trquery param). - Computes a cache key:
sha256(image_path + transformations). - Looks up the transformed image in the cache. On a
GEThit the object is fetched from S3; on aHEADhit the body is never downloaded — Redis stores{s3_key, size, content_type}and S3HeadObjectis used only for legacy markers that lack size. - Otherwise it fetches the source from S3, applies the transform, stores the result in S3, records metadata in Redis, and returns it.
A cron job (CACHE_DELETE_CRON) periodically clears the cache (Redis markers and the matching S3 objects).
| Property name | compatible | comment |
|---|---|---|
| w | ✅ | fixed pixel values, and fractions in (0, 1) are treated as a percentage |
| h | ✅ | fixed pixel values, and fractions in (0, 1) are treated as a percentage |
| q | ✅ | quality of the image, default value is DEFAULT_QUALITY |
| f | ✅ | output format, default is DEFAULT_FORMAT; values: jpeg, jpg, png, webp |
Aligned with ImageKit transformation limits (free-plan values where adjustable):
| Limit | Value | Behavior |
|---|---|---|
| Max image file size for processing | 20 MB | request rejected |
| Max image megapixels for processing | 25 MP | request rejected |
Max transform dimensions (w / h) |
65 535 px | larger absolute values are ignored |
| Max WebP transform / output dimensions | 16 383 px | request rejected |
You can transform an image by calling your URL like this (remember to encode the , as %2C in the query param):
https://yourdomain.com/image-example.png?tr=w-606,h-450,f-jpeg
or
https://yourdomain.com/tr:w-606,h-450,f-jpeg/image-example.png
Required environment variables:
S3_ENDPOINT: URL of the S3 bucket (defaulthttps://ams3.digitaloceanspaces.com)S3_BUCKET: name of the S3 bucketS3_ACCESS_KEY: access key id of the S3 bucketS3_SECRET_KEY: secret key of the S3 bucketREDIS_URL: Redis connection URL
Optional environment variables:
PORT: port to listen on (default3000)S3_REGION: S3 region (defaultams3)DEFAULT_QUALITY: default quality applied to optimize images (default80)DEFAULT_FORMAT: default output format (defaultwebp)CACHE_DELETE_CRON: cron schedule for cache cleanup (default0 1 * * 1, every Monday at 1am). Standard 5-field expressions are supported; a seconds field is optional.CACHED_TIME:max-age(in seconds) advertised on served images (default604800)
See .env.example for a template.
Image processing is powered by native libvips, so it must be installed before building or running locally:
# macOS
brew install vips
# Debian / Ubuntu
sudo apt-get install -y libvips-devOn Homebrew (or any non-standard prefix), point pkg-config at libvips so the build script can find it:
export PKG_CONFIG_PATH="$(brew --prefix vips)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig:$PKG_CONFIG_PATH"Install the toolchain from rustup.rs, then:
# run in development
cargo run
# run the tests
cargo test
# build an optimized release binary
cargo build --releasedocker build -t pixtimize .
docker run --rm -p 3000:3000 --env-file .env pixtimizeThe project ships a nixpacks.toml so it can be deployed on any Nixpacks-based platform (Railway, Coolify, Dokploy, Easypanel, ...).
It configures a few things the default Rust provider does not handle:
- Nix build packages:
cmake+gccto compileaws-lc-rs(TLS), andpkg-configto locate native libraries at build time. - Apt packages:
libvips-devfor the build, pluslibvips42andlibglib2.0-0for runtime (the Nixpacks base image is Ubuntu). PKG_CONFIG_PATHpointing at the Ubuntu multiarch pkgconfig dirs so the nixpkg-configcan see the apt-installed libvips.NIXPACKS_NO_MUSL=1, becauseaws-lc-rsand libvips do not build/link cleanly against the default static musl target.- After
cargo build,scripts/patch-native-binary.shrunspatchelfso the binary uses the system dynamic linker and searches apt’s/usr/lib/...for libvips/glib. Without this, Nixpacks links with a nixld-linuxthat cannot see apt libraries — which surfaces aslibglib-2.0.so.0: cannot open shared object fileeven when the packages are installed.
Do not set LD_LIBRARY_PATH or add vips/glib to nixLibs: both mix glibc variants and crash with __vdso_gettimeofday: invalid mode for dlopen().
The server binds to 0.0.0.0:$PORT, so the platform-provided PORT is used automatically. Set the required environment variables (see Configuration) in your platform's dashboard rather than committing a .env file.
Coolify / production: set the build pack to Dockerfile. The multi-stage Debian image installs libvips42 in the runtime stage cleanly and avoids the nix linker issue entirely.
Licensed under the Apache License 2.0.