This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Mort is an S3-compatible image processing server written in Go. It transforms images on-the-fly using URL-based parameters (presets or query strings) and supports multiple storage backends (S3, local, HTTP, Azure, Google Cloud, etc.). The server handles request collapsing, rate limiting, caching, S3 GLACIER object restoration, and includes an S3-compatible API for listing and uploading files.
New Feature: Automatic S3 GLACIER/DEEP_ARCHIVE object restore - see docs/GLACIER_RESTORE.md
# Run unit tests with race detection and formatting
make unit
# Run unit tests with benchmarks
make unit-bench
# Run integration tests (requires npm dependencies)
make integrations
# Run all tests
make tests
# Run tests in docker
make docker-tests
# Generate coverage report
make coverage# Run specific test with race detection
go test -race -run TestName ./pkg/path/to/package
# Run tests in a single package
go test -race ./pkg/cache/...
# Run with verbose output
go test -v -race -run TestName ./pkg/path/to/package# Run with default config
make run-server
# Run with custom config
go run cmd/mort/mort.go -config path/to/config.yml
# Run test server
make run-test-server
# Run test server with Redis
make run-test-server-redis# Format and vet code
make format
# Build binary
go build -o mort cmd/mort/mort.go- HTTP Request → chi router with middleware (S3 auth, cloudinary upload interceptor)
- FileObject Creation → Parses URL into a FileObject with bucket, key, transforms, and storage config
- Request Processor → Core component that orchestrates the entire processing pipeline
- Response Cache Check → Checks if transformed image is already cached
- Request Collapsing → Multiple concurrent requests for same resource are collapsed into one
- Storage Layer → Retrieves original image from configured storage backend
- Image Engine → Applies transforms using libvips (via bimg)
- Response → Returns transformed image and caches it
The heart of Mort. RequestProcessor handles all incoming requests with:
- Request collapsing using
lock.Lockinterface to prevent duplicate processing - Rate limiting via
throttler.Throttlerto control concurrent image transformations - Response caching to serve repeated requests quickly
- Parent checking to verify original images exist before transformation
- Timeout handling for long-running operations
FileObject represents a parsed request containing:
Bucket: which bucket config to useKey: storage path for the fileTransforms: list of transformations to applyStorage: which storage backend to useParent: reference to original image (for transformed images)
URL parsing supports multiple modes:
- presets: predefined transformations (e.g.,
/bucket/small/image.jpg) - query: query string transforms (e.g.,
/bucket/image.jpg?width=100&height=100) - presets-query: combination of both
- tengo: custom URL parser using Tengo scripting language
Abstraction over multiple storage backends using the stow library:
- Supports: local, local-meta, s3, http, b2, google, azure, sftp, oracle
- Provides: Get, Head, Set, Delete, List operations
- Thread-safe storage client caching via
storageCachemap with RWMutex - Handles S3-compatible API for listing objects
Singleton configuration loaded from YAML with environment variable expansion:
- Bucket configurations with transforms, storages, and access keys
- Storage configurations for different backends
- Server settings (ports, timeouts, cache settings)
- Transform rules (regex patterns, presets)
Wraps bimg (libvips bindings) to perform image transformations:
- Resize, crop, rotate, blur, watermark, format conversion
- Smart cropping using feature detection
- Quality and compression settings
Response caching implementations:
MemoryCache: in-memory cache using ccacheRedisCache: distributed cache using Redis- Caches full HTTP responses including headers and body
Request collapsing implementations:
MemoryLock: in-process lock using sync.MapRedisLock: distributed lock using Redis (with redislock library)- Allows one request to process an image while others wait for the result
- Basic storage: retrieves original images (configured per bucket)
- Transform storage: stores processed/transformed images
- Path prefixes can be configured per storage to organize files
- S3 and B2 require trimming leading slashes from keys
Configuration is YAML-based with environment variable support (${VAR_NAME}). Key concepts:
buckets:
bucket-name:
keys: # S3 API access keys (optional)
- accessKey: "key"
secretAccessKey: "secret"
transform:
path: "regexp pattern with named groups"
kind: "presets|query|presets-query|tengo"
presets: # for preset-based transforms
preset-name:
quality: 80
filters: {...}
storages:
basic: # for original images
kind: "s3|local|http|..."
transform: # for processed images
kind: "local-meta"
pathPrefix: "transforms"Each storage backend has specific required fields (see pkg/config/config.go validateStorage):
local/local-meta: requiresrootPaths3: requiresaccessKey,secretAccessKey,region,endpointhttp: requiresurlb2,google,azure,sftp,oracle: see respective config fields
- Use
testify/assertfor assertions - Table-driven tests are preferred for multiple test cases
- Always run tests with
-raceflag to detect race conditions - Use
t.Parallel()for tests that can run concurrently - Test files are named
*_test.goand placed alongside source files
Requires libvips installed on the system. If encountering build errors with pkg-config --cflags, set:
export CGO_CFLAGS_ALLOW="-Xpreprocessor"The request collapsing prevents the "thundering herd" problem:
- First request acquires lock for an image key
- Subsequent requests wait on a channel for the result
- When processing completes, the result is broadcast to all waiting requests
- Includes timeout handling (default 30s) to prevent indefinite waits
- Only caches successful responses (200 status) with known content length
- Max cache item size configurable (default 5MB)
- Cache key includes transform hash for unique identification
- Supports both memory and Redis-based caching
For transformed images, Mort can verify the original (parent) image exists:
CheckParentflag controls this behavior- Useful for S3 API where listing queries need parent validation
- Trades performance for correctness
RequestTimeout: overall request processing timeout (default 60s)LockTimeout: max wait time for collapsed requests (default 30s)- Only applied to requests with transforms (large file uploads have no timeout)
- Register storage kind in
storageKindsarray inpkg/config/config.go - Add config validation in
validateStorage - Add stow configuration in
getClientfunction inpkg/storage/storage.go - Add config fields to
config.Storagestruct
- Implement URL parser (see
pkg/object/query.go,preset.goas examples) - Register transform kind using
config.RegisterTransformKind() - Add validation in
validateTransform - Parser must populate
FileObject.TransformsandFileObject.Key
- Add filter implementation in
pkg/processor/plugins/or extendpkg/engine/ - Update transform parsing to support new operation
- Filters are applied in order specified in configuration
- memorise commend with flags for tests