Konifer logo

# Konifer ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/dmaiken/konifer/build.yml) ![Codecov](https://img.shields.io/codecov/c/github/dmaiken/konifer) ![GitHub License](https://img.shields.io/github/license/dmaiken/konifer) **The self-hosted image backend your application doesn't have to build.** Adding image uploads means more than putting files in a bucket. Your team needs validation, thumbnails, replacement behavior, metadata, cache rules, and deletion across storage and application records. Konifer provides those capabilities through one HTTP API so you can spend less time building and maintaining image infrastructure. Use your storage and CDN, and choose image paths that fit your application. Address a profile picture by user, a gallery by product, or an individual image by ID. Group related images under shared paths to apply storage and processing policies and delete them together. Deploy Konifer alongside PostgreSQL and S3-compatible storage or a persistent filesystem. Image processing and background variant generation run inside Konifer. [10-minute quickstart](https://konifer.io/docs/start-here/getting-started) · [Documentation](https://konifer.io/) · [Deployment](https://konifer.io/docs/operate/deployment) · [Performance report](https://dmaiken.github.io/konifer/performance/report/) ## Why I built it I've seen the same story on several teams: someone needs to store a product photo, then another feature needs thumbnails, then a third needs different upload limits. Before long, image handling is spread across S3 buckets, Lambdas, queues, and services. Replacing a photo, keeping its history, or deleting a user's images means coordinating those pieces again. With Konifer, you configure those jobs in one service and keep the image handling code out of individual applications: - **Validate uploads in one place:** accept multipart uploads, S3 object ARN imports, or URLs from allowed domains. Configure upload size, image dimension, pixel count, and format limits instead of repeating validation in callers. - **Use a built-in replacement and history model:** keep originals, metadata, and upload history at application-defined paths. Replace a photo without changing its application URL, and retrieve previous entries when you need them. - **Reuse transformations and cached results:** resize, crop, and convert images with libvips. Define named profiles and generate variants after upload or on demand, without building a separate thumbnail-generation workflow. - **Centralize delivery and cleanup:** request content, metadata, links, or redirects to your object store or CDN. Delete an entry, the images at a path, or a path and its children through the API instead of implementing image storage cleanup in each application. You can give avatars, marketplace listings, and tenant branding different storage and processing policies while running the same service. ## Try it Run the 1.0 release candidate with in-memory storage: ```bash docker run --name konifer-quickstart \ --publish 8080:8080 \ --env IN_MEMORY=true \ ghcr.io/dmaiken/konifer:1.0.0-rc.1 ``` In-memory mode loses all uploaded images and metadata when the container stops. Follow the [10-minute quickstart](https://konifer.io/docs/start-here/getting-started) to upload, transform, and replace your first image. ## Choose image paths that fit your application You define the paths you use to store and retrieve images. Use a resource path such as `/assets/users/123/profile-picture` to address a user's current picture. Construct its URL from the user ID without storing a separate image reference: ```http POST /assets/users/123/profile-picture GET /assets/users/123/profile-picture/-/content?width=256&format=webp GET /assets/users/123/profile-picture/-/info ``` Use an image ID of your choosing when you want an independent image reference. Combine image IDs with resource paths to organize individual images within a user's collection: ```http POST /assets/{imageId} GET /assets/{imageId}/-/content POST /assets/users/123/images/{imageId} GET /assets/users/123/images/{imageId}/-/content ``` Each upload gets a Konifer `entryId` within its chosen path. Use the path to fetch the current image or select an entry to retrieve a specific version: ```http GET /assets/users/123/profile-picture/-/entry/0/content ``` Shared parent paths give you a way to configure policies and manage related images. Apply upload limits to user images, choose a bucket for profile pictures, or delete a user's images through one request. If user 123 closes their account, delete the images below `users/123` without assembling a list of individual image IDs: ```http POST /assets/users/123/profile-picture POST /assets/users/123/images/{imageId} DELETE /assets/users/123/-/recursive ``` See the [asset model](https://konifer.io/docs/concepts/Assets/concepts-assets) for collections, upload history, and entry selection. ## Keep image policies out of application code Define storage and processing rules in `konifer.conf` so callers can upload and fetch images without repeating the policy for each image category. This example limits user images to 15 megapixels, stores profile pictures in a dedicated bucket, and generates a WebP thumbnail after upload. Requests for transformed profile pictures must use the named profile: ```hocon variant-profiles { thumbnail { width = 256 height = 256 fit = fill rotate = auto format = webp } } paths { "/users/**" { limits { max-pixels = 15MP } } "/users/*/profile-picture" { object-store { bucket = profiles } transform { eager-variants = [thumbnail] on-demand-variant { mode = profile_only } } } } ``` Mount your configuration at `/app/config/konifer.conf`. With S3-compatible storage, create the `profiles` bucket before using this example. See the [deployment guide](https://konifer.io/docs/operate/deployment) for the storage setup. Fetch the thumbnail with: ```http GET /assets/users/123/profile-picture/-/content?profile=thumbnail ``` Profile pictures inherit the pixel limit from `/users/**`. Add more specific rules for other image categories to choose their buckets, accepted formats, variant generation, caching, and delivery behavior. The [path configuration docs](https://konifer.io/docs/concepts/concepts-path-configuration) cover matching and inheritance. ## Run it in your infrastructure Konifer ships as a Docker image for Linux `amd64` and `arm64`. A persistent deployment needs Konifer, PostgreSQL for asset records, and S3-compatible storage or a filesystem for originals and generated variants. If your team already operates PostgreSQL and object storage, you can use that infrastructure for Konifer. Image processing, background variant generation, and inference (if enabled) run inside the Konifer container. Put your CDN in front for image delivery. Start with one instance and scale horizontally by adding replicas behind a load balancer. Instances share PostgreSQL and image storage. Each instance is stateless. Filesystem deployments require a shared persistent mount accessible to all replicas. | Deployment concern | Documentation | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | | Container setup, networking, replicas, and upgrades | [Deployment guide](https://konifer.io/docs/operate/deployment) | | PostgreSQL and image storage | [Storage configuration](https://konifer.io/docs/reference/reference-variant-storage) | | CPU, memory, temporary storage, and worker concurrency | [Resource tuning](https://konifer.io/docs/reference/image-processing-reference) | | CDN behavior and fetch URL protection | [HTTP caching](https://konifer.io/docs/reference/http-caching) and [URL signing](https://konifer.io/docs/reference/url-signing) | Konifer fits teams that want to own the image lifecycle for avatars, product galleries, tenant branding, or other application assets. If you already have source-image URLs and just want on-the-fly resizing and format conversion, consider an image processor such as [imgproxy](https://imgproxy.net/) or [imagor](https://docs.imagor.net/). Choose a managed image platform if you want a provider to host and manage your delivery infrastructure. See [Who should use Konifer?](https://konifer.io/docs/start-here/is-konifer-right-for-you) for more detail. ## Testing, security, and performance - The [build workflow](.github/workflows/build.yml) runs unit and functional tests, then integration tests against the containerized application with PostgreSQL and S3-compatible storage. The tests exercise native image processing and model inference alongside the HTTP API. - The [image security workflow](.github/workflows/image-security.yml) generates an SPDX software bill of materials and scans it for vulnerabilities. The [image policy workflow](.github/workflows/image-policy.yml) checks licenses and container configuration. - The [performance report](https://dmaiken.github.io/konifer/performance/report/) tracks latency across releases for common image operations. ## Additional capabilities **Image formats and placeholders.** Process JPEG, PNG, WebP, HEIC, AVIF, JPEG XL, and GIF, including animated GIF and WebP. Generate BlurHash and ThumbHash placeholders. See the [transformation reference](https://konifer.io/docs/reference/image-transformation-reference). **Pixels for vision workers.** Request encoded display images and resized raw pixel buffers from the same stored source. Named profiles define dimensions, orientation, color space, channel order, and HWC or CHW layout. Workers handle tensor construction and model-specific scaling and normalization. See the [raw pixel contract](https://konifer.io/docs/concepts/concepts-pixels) and [PyTorch guide](https://konifer.io/docs/guides/guides-pixels-for-inference). **Content classification.** Install the optional SigLIP2 model pack to accept, reject, or label uploads using text prompts, with inference inside the server process. Test prompts and thresholds through the [Rule Evaluation API](https://konifer.io/docs/concepts/concepts-rule-evaluation) before applying [upload rules](https://konifer.io/docs/concepts/concepts-upload-rules). ## Development Konifer ships as a Docker image. For local development, install the native dependencies and download the SigLIP2 model pack if you work on inference or its tests. [CONTRIBUTING.md](CONTRIBUTING.md) has the setup steps, Gradle tasks, and macOS notes. ## Acknowledgments Konifer builds on these open-source projects: - **[libvips](https://github.com/libvips/libvips)**: The image processor behind Konifer's transformations. - **[vips-ffm](https://github.com/lopcode/vips-ffm)**: The Java FFM bindings Konifer uses to call libvips. - **[jOOQ](https://github.com/jooq/jooq)**: Database access and generated query code. - **[Ktor](https://github.com/ktorio/ktor)**: The Kotlin server framework. - **[ONNX Runtime](https://onnxruntime.ai/)**: In-process model inference. ## Contact me Evaluating Konifer for your application? Email me at [daniel@konifer.io](mailto:daniel@konifer.io) or [start a discussion](https://github.com/dmaiken/konifer/discussions). I'd like to hear what you're building and what would block adoption. ## License The Konifer server is released under the AGPL license in [LICENSE](LICENSE). The Konifer client and common module are licensed under Apache 2.0 license in [LICENSE](client/LICENSE) and [LICENSE](common/LICENSE), respectively.