Package and deploy applications¶
The brewlet CLI packages applications for any language. You supply the
built files, a launch descriptor (launch.json) and the target OCI platforms.
Brewlet does not compile your program, discover dependencies or choose a
runtime ID.
The artifact contract¶
An application is a runnable OCI image with:
- One or more app layers and optional dependency layers under
/app. - Exactly one final launch layer containing
launch.json. brewlet.sh/artifact-version=1on its manifest.- An OCI config that exactly mirrors the launch process.
The CLI and shim reject unknown/duplicate descriptor fields, mirror drift, unsafe layers and unsupported platforms. Multi-platform publication is an explicit producer assertion, not evidence that a native dependency is portable.
{
"schemaVersion": 1,
"process": {
"entrypoint": [
"python3"
],
"args": [
"/app/src/main.py"
],
"workingDir": "/app",
"env": [
{
"name": "PYTHONPATH",
"value": "/app/dependencies"
},
{
"name": "PYTHONDONTWRITEBYTECODE",
"value": "1"
}
]
},
"runtimeImages": [
"python-3.13"
]
}
The optional top-level runtimeImages is an allowlist, not a selector. The
workload must still name an approved runtime explicitly.
Omitting it allows any runtime ID; an explicit empty array denies all IDs.
null is rejected.
brewlet build registry.example.com/apps/api:v2 \
--platform linux/amd64 \
--platform linux/arm64 \
--launch launch.json \
--layer dependency=./dependencies:/app/dependencies \
--layer app=./src:/app/src
brewlet push registry.example.com/apps/api:v2 --push-result push.json
Repeated platforms mean the supplied bytes work unchanged on each platform. Different native payloads require separate platform manifests and an explicitly authored index descriptor.
Producer filesystem¶
Use regular world-readable files and searchable directories. The packager rejects unsafe modes, extended attributes and escaping paths instead of silently sanitizing them.
On macOS, the producer runs locally, but the shim and node worker execute on
Linux. The producer ignores the host-local com.apple.provenance and
com.apple.quarantine attributes because layers never carry xattrs; remove any
other extended attribute rather than stripping validation checks.
Independent dependency layer sets¶
brewlet layer-set publishes neutral dependency layers, a config and optional
lock. Passing it to brewlet build --layer-set reuses the compressed layers
byte-for-byte, and the application manifest records which layer set they came
from. Optional signed provenance and the CycloneDX inventory describe the layer
set, not every application built from it.
The kubelet still pulls one application image per container. The shim never resolves dependencies from a package manager or pulls a layer set itself.
Native workload example¶
The following is a template, not directly executable: replace the application digest and select an ID already approved and offered by your platform.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
annotations:
brewlet.sh/runtime-image: python-3.13
spec:
runtimeClassName: brewlet
nodeSelector:
kubernetes.io/arch: arm64
containers:
- name: api
image: registry.example.com/apps/api@sha256:<64-lowercase-hex>
ports:
- containerPort: 8000
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
memory: 256Mi
securityContext:
readOnlyRootFilesystem: true
readinessProbe:
httpGet:
path: /
port: 8000
Admission defaults non-root identity and rejects root groups, privileges, capabilities and startup-fence tolerations. Explicit non-root identities remain subject to the execution contract.
Kubernetes overrides and environment¶
command, args and workingDir retain Kubernetes semantics. In particular,
args replaces the image argument array; it does not append to it.
Runtime Env provides defaults and CRI Env overrides them, except that PATH is
runtime-owned. Explicit Pod PATH is ignored with a warning.
HOME is synthesized from validated account metadata when not explicitly set.
Account metadata is optional: an unknown numeric UID uses /tmp. This does not
create passwd entries or home directories. Absolute executable paths work
without runtime PATH; relative initial/Exec invocation then fails with
RuntimePATHRequired. Author paths explicitly in the descriptor instead of
relying on a runtime image entrypoint script or a shell.
Exec, probes and exec hooks reuse the initial process's effective environment,
identity and leased generation.
Packaged /app is read-only and world-readable. Do not bake credentials into it.
Use scoped Secret volumes for secrets and explicit writable volumes for data.
All app/init/sidecar/ephemeral containers in the Pod require v1 artifacts.
Optional Maven producer¶
Java-specific project knowledge lives in the separately packaged Maven plugin. It can prepare classpath/module argv and dependency layers, or consume one verified complete Java layer-set graph.
mvn package sh.brewlet:brewlet-maven-plugin:0.8.1:push \
-Dbrewlet.image=registry.example.com/apps/orders:v2 \
-Dbrewlet.platforms=linux/amd64,linux/arm64 \
-Dbrewlet.mainClass=example.Main
The plugin requires Java 17 or later and a matching brewlet CLI on the
PATH (or set -Dbrewlet.executable). It does not generate Kubernetes
manifests, choose Pod runtime annotations or apply workloads. Consuming a
managed dependency layer set requires a thin JAR, not a nested-JAR (fat JAR)
application. See Java applications and the
Maven plugin reference.