Deploying Cameleer
Cameleer runs anywhere a container or a single Linux, macOS or Windows binary runs. This guide covers the container image: Docker, Docker Compose, Kubernetes and serverless platforms.
The image
| Name | pmsudhi/cameleer on Docker Hub, also ghcr.io/pmsudhi/cameleer |
| Platforms | linux/amd64, linux/arm64 |
| Contents | one native binary (glibc) on gcr.io/distroless/cc-debian12:nonroot: CA certificates, tzdata, no shell |
| Size | about 20 MB to download; under 12.5 MB built with FEATURES=core (build features) |
| Entrypoint | cameleer (the CLI): run, version, get health, --help |
| Working directory | /routes: mount route files here, read-only is fine |
| Port | 8080: the REST DSL, platform-http and the health endpoints share it |
| User | 65532 (nonroot); any other --user works |
| Filesystem | nothing is written: CAMEL_LOCAL_STATUS=false, logs go to stdout; works with a read-only root filesystem |
| Stop | SIGTERM stops the routes gracefully (about half a second for a REST route) |
| Tags | latest, 0.3.0, 0.3, edge (manual builds of main), sha-<commit> |
| Supply chain | SBOM, max-mode build provenance and a GitHub attestation on every push |
Verify an image's provenance:
gh attestation verify oci://ghcr.io/pmsudhi/cameleer:latest --owner pmsudhiConfiguration
Settings come from, in increasing priority:
application.propertiesorapplication.yaml, passed with--properties /config/application.properties- environment variables:
CAMEL_SERVER_PORT=9090setscamel.server.port, and so on --prop key=valueon the command line
Routes read environment values with {{env:NAME:default}}, as in Apache Camel. Settings can
use them too: camel.server.port={{env:PORT:8080}}.
Health endpoints need the management server on:
--prop camel.server.enabled=true --prop camel.server.healthCheckEnabled=trueThis serves /q/health, /q/health/live and /q/health/ready on port 8080. Readiness is
DOWN (HTTP 503) while a route is stopped. cameleer get health --url http://localhost:8080
exits with status 1 when it is DOWN, so it can serve as a Docker health check.
Don't turn on camel.server.devConsoleEnabled (or cameleer run --console) in production
without a network policy in front: the dev console has unauthenticated endpoints that stop
and start routes.
Docker
docker run --rm -p 8080:8080 --read-only \
-v "$PWD/routes:/routes:ro" \
-e OPENAI_API_KEY \
pmsudhi/cameleer run /routes/my-route.camel.yaml- Mount route folders read-only (
:ro) unless a route writes there. For a route that writes into a mounted folder (the file component'smove, for example), run as your own user so the files are yours:--user "$(id -u):$(id -g)". - Reach services on the host with
--network hoston Linux. With Docker Desktop on macOS or Windows, usehost.docker.internalinstead. Kafka brokers must also advertise an address the container can reach.
Baking routes into your own image
For production, ship an image that contains the routes:
FROM pmsudhi/cameleer:0.3.0
COPY routes/ /routes/
CMD ["run", "/routes/my-route.camel.yaml", "--prop", "camel.server.enabled=true", "--prop", "camel.server.healthCheckEnabled=true"]Docker Compose
services:
orders:
image: pmsudhi/cameleer:0.3.0
command: ["run", "/routes/rest-api.camel.yaml",
"--prop", "camel.server.enabled=true", "--prop", "camel.server.healthCheckEnabled=true"]
ports: ["8080:8080"]
volumes: ["./routes:/routes:ro"]
read_only: true
healthcheck:
test: ["CMD", "/usr/local/bin/cameleer", "get", "health", "--url", "http://localhost:8080"]
interval: 10s
timeout: 3s
retries: 3Kubernetes
apiVersion: v1
kind: ConfigMap
metadata:
name: orders-routes
data:
rest-api.camel.yaml: |
# paste examples/rest-api.camel.yaml here
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders
spec:
replicas: 2
selector:
matchLabels: { app: orders }
template:
metadata:
labels: { app: orders }
spec:
terminationGracePeriodSeconds: 30
securityContext:
runAsNonRoot: true
seccompProfile: { type: RuntimeDefault }
containers:
- name: cameleer
image: pmsudhi/cameleer:0.3.0
args: ["run", "/routes/rest-api.camel.yaml",
"--prop", "camel.server.enabled=true",
"--prop", "camel.server.healthCheckEnabled=true"]
ports:
- { name: http, containerPort: 8080 }
env:
- name: OPENAI_API_KEY
valueFrom: { secretKeyRef: { name: openai, key: api-key, optional: true } }
resources:
requests: { cpu: 50m, memory: 32Mi }
limits: { memory: 256Mi }
readinessProbe:
httpGet: { path: /q/health/ready, port: http }
periodSeconds: 5
livenessProbe:
httpGet: { path: /q/health/live, port: http }
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
volumeMounts:
- { name: routes, mountPath: /routes, readOnly: true }
volumes:
- name: routes
configMap: { name: orders-routes }A route uses about 10 MB at rest, most of it pages of the binary itself; the private memory of a running route is 1–2 MB. Size the memory limit for your message sizes and concurrency, not for a JVM.
Serverless
- Google Cloud Run, Knative, Azure Container Apps, Fly.io: these run the image as it is. Cloud Run sends
requests to
PORT, which is 8080 unless you change it. If you do change it, add--prop camel.server.port={{env:PORT:8080}}. Startup takes milliseconds, so scale-to-zero has no practical cold-start cost from Cameleer itself. - AWS Lambda: see below.
cameleeris its own Lambda custom runtime.
AWS Lambda
cameleer serves Lambda invocations itself on the provided.al2023 runtime: no runtime
library, adapter or layer. When Lambda sets AWS_LAMBDA_RUNTIME_API, cameleer run starts the
routes and then loops over the
Runtime API instead of
waiting for a signal.
- HTTP events (API Gateway REST and HTTP APIs, function URLs, ALB) go to your
platform-httproutes as if they had come over a socket, and the HTTP response becomes the proxy-integration response. The same route file runs on Lambda and in a container. - Other events (S3, SQS, EventBridge, a direct invoke) are sent request-reply to the
handler endpoint with the event JSON as the body; the reply body is the function result. The
handler endpoint is the function's Handler setting when it is an endpoint URI (for example
direct:orders), elsecamel.main.lambda-handler, elsedirect:lambda. - A failed exchange is reported to the invocation's
/errorendpoint (errorTypeis the exception kind); a startup failure (a bad route file, an unknown component) to/init/error. - Exchange headers:
CamelAwsLambdaRequestId,CamelAwsLambdaDeadlineMs,CamelAwsLambdaFunctionArn,CamelAwsLambdaTraceId, and when Lambda sends themCamelAwsLambdaClientContextandCamelAwsLambdaCognitoIdentity. - Status files for
cameleer psare off on Lambda (the file system is read-only); logs go to CloudWatch through stderr.
# orders.camel.yaml: an S3 or EventBridge event in, a JSON summary out
- route:
from:
uri: direct:lambda
steps:
- setBody:
simple: '{"bucket":"${jsonpath($.Records[0].s3.bucket.name)}","ok":true}'Package and deploy (the binary must be a static musl build: AL2023 has glibc 2.34, older than what current build machines link against):
scripts/package-lambda.sh --arch x86_64 --features cloud --out function.zip orders.camel.yaml
aws lambda create-function --function-name orders --runtime provided.al2023 \
--architectures x86_64 --handler direct:lambda --zip-file fileb://function.zip \
--role arn:aws:iam::<account>:role/<role>The zip holds bootstrap (the cameleer binary under the name Lambda starts; with no
arguments it runs the route files in $LAMBDA_TASK_ROOT) and your route files. --features
keeps only the component families the routes use (see build features), which
makes the zip and the cold start smaller. Measured init times are in
BENCHMARKS.md.
camel.main.duration-max-messages stops after that many invocations (useful in tests). Lambda
sends SIGTERM before it shuts a sandbox down; routes are stopped gracefully then.
Smaller builds
The default build and image contain every component family. A function, an edge device or a
slim image can leave the rest out with the cargo features of cameleer-main. The
build features guide lists every feature and what it adds, shows how
to choose them for your routes, and has the commands for each kind of build. In short:
docker build --build-arg FEATURES=cloud,sql,jq -t my-cameleer .
cargo install cameleer-main --no-default-features --features cloud,sql,jq
scripts/package-lambda.sh --features cloud,jq --out function.zip orders.camel.yaml- A feature list replaces the default: name every family you use. The template engines,
jq, the core XPath subset,http2andvalidate(cameleer validate,cameleer schema) are features too. - A route that uses something left out fails at startup with the feature to add, for example
No component found with scheme: kafka (this cameleer binary was built without the `kafka-mqtt` feature; rebuild cameleer-main with `--features kafka-mqtt`). cameleer versionlists the features of a build.FEATURES=coreis the smallest: 5.8 MB (glibc) or 6.0 MB (static musl) on Linux x86-64, against 23.9 MB with every component. More sizes and cold-start times are in BENCHMARKS.md.
Without a container
cargo install cameleer-main
cameleer run my-route.camel.yamlCamel JBang habits carry over with the cameleer command: cameleer ps, cameleer log,
cameleer stop. Those commands use status files under ~/.camel (CAMEL_HOME). Set
CAMEL_LOCAL_STATUS=false to run without them.
Found a problem?
If something on this page doesn't match what Cameleer does, or you hit a bug while building
with it, please help fix it, whether you are a person or a coding agent:
open an issue that describes what you saw,
then fork the repository, fix it, add or update a test, run cargo test --workspace, and open
a pull request. CONTRIBUTING.md
explains the ground rules.