Configuration
emgr is configured entirely through environment variables, read by src/modules/env/env.rs via envconfig. That file is the source of truth this page is generated against - a CI check (.github/workflows/ci.yml's docs-env-drift job, .github/scripts/check_env_docs.py) fails the build if this table drifts from it in either direction.
This page previously documented variables that don't exist
CACHE_ENABLED, CACHE_TTL_SECONDS, S3_BUCKET, S3_REGION and LOCAL_STORAGE_PATH were listed here before but were never read by the code (GH #47). There is no response-cache TTL setting today (see GH #40); the real storage variables are MINIO_BUCKET, MINIO_REGION and LOCAL_FS_STORAGE_PATH, documented below.
Server
| Variable | Description | Default |
|---|---|---|
HOST | Interface the HTTP server binds to. | 0.0.0.0 |
PORT | HTTP server port. | 3000 |
CDN_BASE_URL | Base URL used to build the Location header on a successful resize - see Redirect status code (a 301, not 302). | http://localhost:9000/image-cache |
Storage backend selection
| Variable | Description | Default |
|---|---|---|
STORAGE_TYPE | Which backend to use. Accepted values: LOCAL_FS (aliases LOCALFS, LOCAL), S3 (alias MINIO), IN_MEMORY (aliases INMEMORY, MEMORY - test/dev only; refuses to start in a release build). Only takes effect for whichever storage feature(s) the binary was compiled with. | unset |
STORAGE_SUB_PATH | Prefix prepended to every generated cache key, for either backend. | "" (empty) |
Local filesystem storage
Requires the binary to be built with --features local_fs.
| Variable | Description | Default |
|---|---|---|
LOCAL_FS_STORAGE_PATH | Local path resized images are written to. | ./data/images |
MinIO / S3 storage
Requires the binary to be built with --features s3. MinIO and AWS S3 share the same client (MinIO is S3-compatible), so there is a single set of variables for both.
| Variable | Description | Default |
|---|---|---|
MINIO_ENDPOINT_URL | S3-compatible endpoint URL. | http://localhost:9000 |
MINIO_ACCESS_KEY_ID | Access key. | minioadmin |
MINIO_SECRET_ACCESS_KEY | Secret key. | minioadmin |
MINIO_BUCKET | Bucket resized images are written to. | image-cache |
MINIO_REGION | Bucket region. | us-east-1 |
Signed URLs
Added under GH #27, alongside the imgproxy-compatible signed-path URL scheme itself (see the API reference). Signing is the default, not opt-in: with neither a key/salt configured nor ALLOW_UNSIGNED_REQUESTS=true set, the process refuses to start rather than silently serving 403 to every request.
| Variable | Description | Default |
|---|---|---|
SIGNING_KEY | Hex-encoded HMAC-SHA256 key used to verify signed URLs. Required unless ALLOW_UNSIGNED_REQUESTS=true. imgproxy equivalent: IMGPROXY_KEY. | unset |
SIGNING_SALT | Hex-encoded salt mixed into every signed URL's HMAC input. Required unless ALLOW_UNSIGNED_REQUESTS=true. imgproxy equivalent: IMGPROXY_SALT. | unset |
ALLOW_UNSIGNED_REQUESTS | Opt-in escape hatch for local development: when true, a request whose signature segment is the literal unsigned bypasses verification entirely. Does not weaken verification of a real signature - it only widens the unsigned escape path. | false |
SSRF / source-fetch guard
Added under GH #21 to stop the url query parameter from being used to reach internal services. Mirrors imgproxy's equivalent settings, named in each row below.
| Variable | Description | Default |
|---|---|---|
MAX_REDIRECTS | Maximum redirects the source fetch follows; every hop is re-validated (scheme, allowlist, resolved address). imgproxy: IMGPROXY_MAX_REDIRECTS. | 5 |
ALLOWED_SOURCES | Comma-separated allowlist of source URL prefixes. Unset allows any http(s) URL, still subject to the private-range guard below. A host matching an entry here is also exempted from the private-IP-range block (RFC1918/CGNAT/IPv6 ULA) for that hop, so an explicitly-named internal origin (a Kubernetes Service ClusterIP, an internal MinIO, a private CDN shield) is reachable (GH #57) - loopback and link-local are unaffected and keep their own flags below. Re-checked on every redirect hop. imgproxy: IMGPROXY_ALLOWED_SOURCES. | unset |
ALLOW_LOOPBACK_SOURCE_ADDRESSES | Opt-in to allow fetching from loopback addresses (blocked by default). | false |
ALLOW_LINK_LOCAL_SOURCE_ADDRESSES | Opt-in to allow fetching from link-local addresses (blocked by default). | false |
Resolution and output limits
Added under GH #26. See the API reference for how these interact with the width/height query parameters.
| Variable | Description | Default |
|---|---|---|
MAX_SRC_RESOLUTION_MP | Maximum decoded source resolution in megapixels, checked against header dimensions before a full decode. imgproxy default: IMGPROXY_MAX_SRC_RESOLUTION = 50. | 50 |
MAX_OUTPUT_WIDTH | Maximum requested output width in pixels. | 4096 |
MAX_OUTPUT_HEIGHT | Maximum requested output height in pixels. | 4096 |
MAX_ANIMATION_FRAMES | Maximum number of frames read from an animated GIF/WebP source before the animated encode path (GH #49) refuses the request. Enforced while iterating frames, not after decoding all of them, so it bounds work spent on attacker-supplied input rather than just rejecting after the fact - a many-tiny-frames animation can be individually well within MAX_SRC_RESOLUTION_MP per frame while still being a real memory/CPU amplification via frame count alone. | 512 |
Watermarking, presets and the processing-option allowlist
Added under GH #52. See the API reference for the wm:/wmu:, pr: request-option syntax these variables back.
| Variable | Description | Default |
|---|---|---|
WATERMARK_URL | Default watermark image URL, used when a request sets wm: without its own wmu:{base64url}. Fetched through the same SSRF guard (ALLOWED_SOURCES, private-range block, redirect re-validation) as any other source URL. A request's own wmu: always takes priority over this default. imgproxy: IMGPROXY_WATERMARK_URL. | unset |
PRESETS | Preset definitions: comma-separated {name}={options} entries, {options} itself /-separated processing-option segments - e.g. thumbnail=rs:fill:300:300/q:80,default=el:1. A preset named default is special: it is prepended ahead of every request's own segments automatically, even when the request never names a preset at all. A preset's own definition cannot contain a pr: segment (presets don't recurse). imgproxy: IMGPROXY_PRESETS. | unset |
ALLOWED_PROCESSING_OPTIONS | Comma-separated allowlist of processing-option short codes (e.g. rs,q,pr) permitted directly in a request URL. Unset/blank means unrestricted. Restricts what a request can do directly - it does not apply to options used inside a preset's own definition, which is what lets an operator hand out a restricted set of presets while forbidding the raw options they're built from. imgproxy: IMGPROXY_ALLOWED_PROCESSING_OPTIONS. | unset (unrestricted) |
JPEG encoding
Added under GH #76. JPEG output is encoded via mozjpeg/libjpeg-turbo rather than the image crate's own encoder, which has no progressive-mode switch and hardcodes 4:2:2 chroma subsampling. See the API reference for the jpgo:{progressive}:{no_subsample} and mb:{bytes} request-option syntax these variables set the deployment-wide default for - a request's own jpgo: segment always overrides these when present.
| Variable | Description | Default |
|---|---|---|
JPEG_PROGRESSIVE | Encode JPEG output progressively (multi-scan) instead of baseline sequential when a request doesn't set jpgo:'s progressive slot itself. Progressive JPEGs render incrementally and are often, but not always, smaller - see this change's own benchmark report rather than assuming a size win on every image. imgproxy: IMGPROXY_JPEG_PROGRESSIVE. | false |
JPEG_NO_SUBSAMPLING | Encode JPEG chroma at full resolution (4:4:4) instead of this crate's default 4:2:2 when a request doesn't set jpgo:'s no_subsample slot itself. 4:4:4 preserves colour detail on saturated edges (screenshots, logos, text on colour) at the cost of a larger file. imgproxy: IMGPROXY_JPEG_NO_SUBSAMPLING. | false |
Performance tuning
PERFORMANCE_PROFILE selects a preset (see src/config/performance.rs); every other variable in this section, if set, overrides that preset's value for just that one field.
| Variable | Description | Default |
|---|---|---|
PERFORMANCE_PROFILE | One of high_throughput, low_latency, memory_efficient (case-insensitive). Unset (or blank/whitespace-only) falls back to per-field defaults below rather than a named preset. An unrecognised, non-blank value (e.g. a typo like hgh_throughput) refuses to start the process at all, mirroring SigningConfig/MetricsAuthConfig's fail-closed convention, rather than silently falling back to per-field defaults with a different effective configuration than the operator asked for — issue #83. | unset |
MAX_CONCURRENT_DOWNLOADS | Maximum concurrent source-image downloads. | 20 |
MAX_CONCURRENT_PROCESSING | Maximum concurrent image-processing tasks. | Host core count |
HTTP_TIMEOUT_SECS | Timeout for the source-image HTTP client. | 30 |
MAX_IMAGE_SIZE_MB | Maximum accepted source image size. | 50 |
CPU_THREAD_POOL_SIZE | CPU-bound thread pool size for decode/resize/encode. | Host core count |
ENABLE_HTTP2 | Enable HTTP/2 for the source-image HTTP client. | true (false under the memory_efficient profile, which trades it away for lower per-connection memory) |
CONNECTION_POOL_SIZE | Source-image HTTP client's per-host connection pool size. | 50 |
KEEP_ALIVE_TIMEOUT_SECS | Source-image HTTP client's keep-alive timeout. | 60 |
Observability
Requires the binary to be built with --features otel. See Docker deployment for how compose.yaml wires these to the bundled Jaeger instance.
| Variable | Description | Default |
|---|---|---|
LOG_LEVEL | Log verbosity (trace, debug, info, warn, error). | debug |
OTLP_SPAN_ENDPOINT | OTLP gRPC endpoint for traces. | http://localhost:4317 |
OTLP_METRIC_ENDPOINT | OTLP HTTP endpoint for metrics. | http://localhost:4318/v1/metrics |
OTLP_SERVICE_NAME | Service name reported in traces/metrics. | rust-app-example |
/metrics and /health authentication
Added under GH #77, a leftover from #27's stated scope. /metrics is a Prometheus endpoint - it exposes request rates, cache hit ratios, error counts and latency histograms, which is precise reconnaissance for an attacker probing for expensive requests. Mirroring SIGNING_KEY/SIGNING_SALT (see Signed URLs above), requiring a token is the default, not opt-in: with neither METRICS_AUTH_TOKEN configured nor ALLOW_UNAUTHENTICATED_METRICS=true set, the process refuses to start rather than silently serving traffic telemetry to anyone who asks. This only applies to builds with --features otel - a build without it never mounts /metrics at all, so there is nothing to protect and no startup check to satisfy.
| Variable | Description | Default |
|---|---|---|
METRICS_AUTH_TOKEN | Bearer token /metrics requires via Authorization: Bearer <token>. Compared in constant time. Required unless ALLOW_UNAUTHENTICATED_METRICS=true. Configure the same value in your Prometheus scraper's authorization scrape config. | unset |
ALLOW_UNAUTHENTICATED_METRICS | Opt-in escape hatch: when true, /metrics is served without requiring a token at all. Does not weaken verification of a real token - it only ever widens the unauthenticated-access escape path. | false |
A request with a missing or invalid token gets 401 Unauthorized with a WWW-Authenticate: Bearer header, not 403 - unlike signed-URL rejection, there's a real credential the caller can supply on a retry.
/health is left unauthenticated on purpose, not by oversight: it is the target of Kubernetes liveness/readiness/startup probes (helm/serverless/templates/knative-service.yaml), which hit it directly over HTTP and have no practical way to carry a bearer token. It also only ever returns the literal string OK - none of the traffic/cache/latency detail /metrics exposes - so the risk the two endpoints present isn't comparable. If /health ever needs restricting, do it at the network layer (an ingress rule or NetworkPolicy scoped to the cluster's own probe traffic), not with application-layer auth that would break the probes it exists to serve.
Example .env file
See .env.example in the repository root for a copy-pasteable starting point covering every variable above. Copy it to .env (which is gitignored - never commit real credentials there).
STORAGE_TYPE=LOCAL_FS
LOCAL_FS_STORAGE_PATH=./data/images
CDN_BASE_URL=http://localhost:3000/api/images/files
Docker environment variables
When running with Docker, pass environment variables with -e:
docker run -p 3000:3000 \
-e STORAGE_TYPE=LOCAL_FS \
-e LOCAL_FS_STORAGE_PATH=/app/data/images \
-e CDN_BASE_URL=http://localhost:3000/api/images/files \
ghcr.io/vaam-store/image-resizer:fs-latest
Helm chart configuration
When deploying with Helm, configure the service via values.yaml's configMaps.config.data block. See the Helm Chart documentation for details.