Deployment
Run Firn locally with Docker Compose or deploy to production as a standalone container.
Local development
The Docker Compose file launches MinIO (local S3) alongside the Firn API. No host Rust toolchain is required.
git clone https://github.com/gordonmurray/firnflow
cd firnflow
docker compose up --build
This starts three services:
| Service | Port | Purpose |
|---|---|---|
minio | 9000 (S3 API), 9001 (console) | S3-compatible object storage |
minio-init | - | One-shot: creates the firnflow bucket |
firnflow | 3000 | Firn API server |
MinIO console: http://localhost:9001 (credentials: minioadmin / minioadmin)
Running just MinIO
If you want to run Firn outside Docker (e.g. during development with a host Rust toolchain), start only MinIO:
# Start MinIO only
docker compose up -d minio minio-init
# Run Firn directly
FIRNFLOW_S3_BUCKET=firnflow \
FIRNFLOW_S3_ENDPOINT=http://127.0.0.1:9000 \
FIRNFLOW_S3_ACCESS_KEY=minioadmin \
FIRNFLOW_S3_SECRET_KEY=minioadmin \
cargo run -p firnflow-api
Production Docker
The included Dockerfile is a multi-stage build that produces a minimal production image.
Build the image
docker build -t firnflow-api .
Image details
| Stage | Base image | Purpose |
|---|---|---|
| Builder | rust:1.94-bookworm | Compiles the release binary with protobuf support |
| Runtime | debian:bookworm-slim | Minimal image with just ca-certificates for TLS |
Run the container
docker run -d \
--name firn \
-p 3000:3000 \
-e FIRNFLOW_S3_BUCKET=my-production-bucket \
-e FIRNFLOW_S3_REGION=eu-west-1 \
-e FIRNFLOW_CACHE_MEMORY_BYTES=268435456 \
-e FIRNFLOW_CACHE_NVME_BYTES=10737418240 \
-v firn-cache:/var/lib/firnflow/cache \
firnflow-api
/var/lib/firnflow/cache for the NVMe tier. The cache is ephemeral: losing it only means cache misses until it warms up again. For best performance, use a tmpfs or NVMe-backed volume.
AWS deployment
For production on AWS, Firn works best with IAM-based credentials (instance profiles, ECS task roles, or IRSA for EKS). No access keys needed.
Prerequisites
- An S3 bucket with public access blocked
- An IAM role with
s3:GetObject,s3:PutObject,s3:DeleteObject,s3:ListBucketon the bucket - A compute environment (ECS, EKS, EC2) with the IAM role attached
Minimal IAM policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-firn-bucket",
"arn:aws:s3:::my-firn-bucket/*"
]
}
]
}
ECS task definition (excerpt)
{
"containerDefinitions": [
{
"name": "firn",
"image": "firnflow-api:latest",
"portMappings": [{"containerPort": 3000}],
"environment": [
{"name": "FIRNFLOW_S3_BUCKET", "value": "my-firn-bucket"},
{"name": "FIRNFLOW_S3_REGION", "value": "eu-west-1"},
{"name": "FIRNFLOW_CACHE_MEMORY_BYTES", "value": "268435456"},
{"name": "FIRNFLOW_CACHE_NVME_BYTES", "value": "10737418240"}
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
"interval": 10,
"timeout": 3,
"retries": 3
},
"mountPoints": [
{
"sourceVolume": "cache",
"containerPath": "/var/lib/firnflow/cache"
}
]
}
]
}
Health checks
Use GET /health for both liveness and readiness probes. The endpoint returns 200 with body ok as soon as the server is listening.
Docker Compose
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 10s
timeout: 3s
retries: 3
Kubernetes
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
Cache warmup after deployment
After a fresh deployment or restart, the cache is empty. Use the /ns/{ns}/warmup endpoint to pre-populate it with your most common queries:
curl -X POST http://localhost:3000/ns/production/warmup \
-H 'Content-Type: application/json' \
-d '{
"queries": [
{"vector": [1.0, 0.0, ...], "k": 10},
{"vector": [0.0, 1.0, ...], "k": 10}
]
}'
This returns immediately (202) and runs the queries in the background, populating the cache as it goes. Monitor firnflow_cache_misses_total to track warmup progress.
Recommended operational setup
- Reverse proxy: put nginx, Caddy, or an ALB in front of Firn for TLS termination and rate limiting
- Prometheus scraping: scrape
/metricsat 15s intervals - Log aggregation: Firn logs structured JSON via
tracing. Ship to your log aggregator. - Cache volume: use a fast local SSD or NVMe, not networked storage
- Compaction schedule: run
/ns/{ns}/compactperiodically for write-heavy namespaces