Skip to content

Commit 9276c11

Browse files
hyperpolymathclaude
andcommitted
feat(k9-svc): container deployment example, deprecation analysis, Chainguard fix
Add container-deploy.k9.ncl (Hunt-level multi-service deployment with rolling deploy and rollback) and NOT-a-good-fit.adoc (honest comparison of k9-svc/a2ml vs alternatives with 5 DEPRECATE and 10 KEEP verdicts). Update GUIDE.adoc with container deployment section. Fix Containerfile to use Chainguard wolfi-base instead of Debian. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2c9962a commit 9276c11

4 files changed

Lines changed: 1230 additions & 27 deletions

File tree

k9-svc/Containerfile

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,104 @@
1-
# SPDX-License-Identifier: AGPL-3.0-or-later
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
22
# Containerfile - K9 SVC Runtime Container
33
#
4+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)
5+
#
6+
# Uses Chainguard base images for minimal attack surface:
7+
# - Builder stage: Rust toolchain on wolfi-base for compiling nickel and just
8+
# - Runtime stage: wolfi-base with only the binaries needed to run k9 components
9+
#
10+
# Chainguard images have no shell (in static variants), no package manager
11+
# bloat, and minimal CVE surface. We use wolfi-base (not static) because
12+
# k9-svc needs a shell for the must shim and Just recipes.
13+
#
414
# Build: podman build -t k9-svc:latest .
515
# Run: podman run --rm -it k9-svc:latest status
616
# Mount: podman run --rm -it -v ./components:/k9/components k9-svc:latest validate-all
717

8-
FROM docker.io/library/rust:slim-bookworm AS builder
18+
# ─────────────────────────────────────────────────────────────────────
19+
# Builder Stage
20+
#
21+
# Compiles nickel-lang-cli and just from source using Rust.
22+
# This stage is discarded after the binaries are copied out.
23+
# ─────────────────────────────────────────────────────────────────────
24+
FROM cgr.dev/chainguard/wolfi-base:latest AS builder
925

10-
# Install build dependencies
11-
RUN apt-get update && apt-get install -y --no-install-recommends \
26+
# Install build dependencies via apk (wolfi's package manager).
27+
# - rust and cargo for compiling nickel and just
28+
# - curl and ca-certificates for downloading crate dependencies
29+
# - gcc and musl-dev for linking native code
30+
RUN apk update && apk add --no-cache \
31+
rust \
32+
cargo \
1233
curl \
1334
ca-certificates \
14-
&& rm -rf /var/lib/apt/lists/*
35+
gcc \
36+
musl-dev
1537

16-
# Install Nickel
38+
# Install Nickel (the typed configuration language that powers k9 validation)
1739
RUN cargo install nickel-lang-cli --locked
1840

19-
# Install Just
41+
# Install Just (the task runner that powers k9 recipes)
2042
RUN cargo install just --locked
2143

22-
# ─────────────────────────────────────────────────────────────
44+
# ─────────────────────────────────────────────────────────────────────
2345
# Runtime Stage
24-
# ─────────────────────────────────────────────────────────────
25-
FROM docker.io/library/debian:bookworm-slim
46+
#
47+
# Minimal Chainguard image with only the binaries and files needed
48+
# to run k9 components. No build tools, no package manager bloat.
49+
# ─────────────────────────────────────────────────────────────────────
50+
FROM cgr.dev/chainguard/wolfi-base:latest
2651

2752
LABEL org.opencontainers.image.title="K9 SVC Runtime"
2853
LABEL org.opencontainers.image.description="Self-Validating Component runtime environment"
2954
LABEL org.opencontainers.image.version="1.0.0-alpha"
3055
LABEL org.opencontainers.image.vendor="hyperpolymath"
31-
LABEL org.opencontainers.image.licenses="AGPL-3.0-or-later"
56+
LABEL org.opencontainers.image.licenses="PMPL-1.0-or-later"
3257
LABEL org.opencontainers.image.source="https://github.com/hyperpolymath/k9-svc"
3358

34-
# Install runtime dependencies
35-
RUN apt-get update && apt-get install -y --no-install-recommends \
59+
# Install minimal runtime dependencies via apk.
60+
# - ca-certificates: for TLS verification when Hunt components access network
61+
# - file: for MIME type detection (used by `file --mime-type` in k9 validation)
62+
# - libgcc: runtime library needed by compiled Rust binaries
63+
RUN apk update && apk add --no-cache \
3664
ca-certificates \
3765
file \
38-
&& rm -rf /var/lib/apt/lists/*
39-
40-
# Copy binaries from builder
41-
COPY --from=builder /usr/local/cargo/bin/nickel /usr/local/bin/nickel
42-
COPY --from=builder /usr/local/cargo/bin/just /usr/local/bin/just
66+
libgcc
4367

44-
# Create non-root user for security
45-
RUN useradd -m -s /bin/sh k9user
68+
# Create non-root user for security.
69+
# k9 components should never run as root unless explicitly required
70+
# by a Hunt-level component (and even then, avoid it).
71+
RUN adduser -D -s /bin/sh k9user
4672
USER k9user
4773
WORKDIR /home/k9user/k9
4874

49-
# Copy K9 SVC files
75+
# Copy binaries from builder stage.
76+
# Only nickel and just are needed at runtime — no Rust toolchain.
77+
COPY --from=builder /root/.cargo/bin/nickel /usr/local/bin/nickel
78+
COPY --from=builder /root/.cargo/bin/just /usr/local/bin/just
79+
80+
# Copy K9 SVC files into the container.
81+
# These are the core schemas, the must shim, and the examples.
5082
COPY --chown=k9user:k9user must justfile pedigree.ncl register.ncl leash.ncl ./
5183
COPY --chown=k9user:k9user mime/ ./mime/
5284
COPY --chown=k9user:k9user examples/ ./examples/
5385

54-
# Verify the triad is functional
86+
# Verify the triad is functional.
87+
# This is the dogfooding step: the container validates itself during build.
88+
# If any of these fail, the build fails — no broken images ship.
5589
RUN ./must status && \
5690
nickel typecheck pedigree.ncl && \
5791
nickel typecheck register.ncl && \
5892
nickel typecheck leash.ncl
5993

60-
# Default: show status
94+
# Default entrypoint: the must shim, which detects the environment
95+
# and delegates to just/nickel as appropriate.
6196
ENTRYPOINT ["./must"]
6297
CMD ["status"]
6398

64-
# Volume for mounting external components
99+
# Volume for mounting external components.
100+
# Users mount their .k9 and .k9.ncl files here for validation/deployment.
65101
VOLUME ["/home/k9user/k9/components"]
66102

67-
# Expose nothing by default (K9 is not a server)
68-
# Components can override if they need ports
103+
# Expose nothing by default (K9 is not a server).
104+
# Hunt-level components that need ports can override this.

k9-svc/GUIDE.adoc

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)
23
= K9 SVC User Guide
34
:toc: left
45
:toclevels: 3
@@ -551,6 +552,170 @@ See `pedigree.ncl` for the full schema with contracts.
551552
│ └── bob.pub
552553
----
553554

555+
== Container Deployment with k9-svc
556+
557+
This section covers how k9-svc wraps container deployments using the Leash
558+
security model, selur-compose, and Nickel typed configuration.
559+
560+
=== When to Use k9-svc for Containers
561+
562+
k9-svc is designed for **operational deployment components** — things that
563+
orchestrate, validate, and deploy container stacks. It is NOT a replacement
564+
for `Cargo.toml`, `mix.exs`, or other build-time manifests.
565+
566+
Use k9-svc when you need:
567+
568+
* **Typed deployment configs** — Nickel contracts catch invalid port numbers,
569+
missing environment variables, and impossible resource limits at eval time,
570+
before any container starts.
571+
* **Security-level enforcement** — The Leash system prevents accidental
572+
execution of destructive deployment recipes.
573+
* **Signed deployment artifacts** — Ed25519 signatures ensure only authorized
574+
operators can run deployment scripts.
575+
* **Self-validating components** — The dogfooding principle means each
576+
deployment component can validate its own configuration.
577+
578+
=== Security Level Selection Guide
579+
580+
Choosing the correct security level is the most important decision when
581+
creating a k9 container deployment component.
582+
583+
[cols="1,2,3"]
584+
|===
585+
| Level | Use When | Container Example
586+
587+
| `'Kennel`
588+
| The component is pure data with no execution.
589+
| A static list of container image tags and their SHA256 digests. No commands, no evaluation. Just data.
590+
591+
| `'Yard`
592+
| The component needs Nickel evaluation but performs no I/O.
593+
| A typed configuration that validates port ranges, memory limits, and environment variable shapes. It produces validated JSON output but does not start any containers.
594+
595+
| `'Hunt`
596+
| The component executes commands, accesses the network, or writes files.
597+
| A deployment component that generates selur-compose files, pulls images, starts services, runs health checks, and performs rollback on failure.
598+
|===
599+
600+
[IMPORTANT]
601+
====
602+
**Rule of thumb:** If your component runs `selur-compose`, `podman`, or any
603+
shell command, it MUST be `'Hunt` level. If it only evaluates Nickel
604+
expressions and produces output, use `'Yard`. If it is pure data (a list of
605+
images, a set of environment variables), use `'Kennel`.
606+
607+
**Never use `'Hunt` for read-only operations.** If your component only reads
608+
and displays data, `'Kennel` or `'Yard` is correct. Hunt requires
609+
cryptographic signatures and should be reserved for components with real
610+
side effects.
611+
====
612+
613+
=== Example: Container Deployment as a Hunt Component
614+
615+
The `container-deploy.k9.ncl` example demonstrates a realistic multi-service
616+
deployment component. It deploys an app + database + cache stack across
617+
dev/staging/production environments.
618+
619+
[source,nickel]
620+
----
621+
# Security: Hunt level because this component:
622+
# - Runs selur-compose commands (subprocess)
623+
# - Pulls container images (network)
624+
# - Writes deployment state files (filesystem)
625+
security = {
626+
trust_level = 'Hunt,
627+
allow_network = true,
628+
allow_filesystem_write = true,
629+
allow_subprocess = true,
630+
signature = "REAL-ED25519-SIGNATURE-HERE",
631+
},
632+
----
633+
634+
Key features of this component:
635+
636+
* **Environment-specific overrides** — Dev (1 replica, debug logging),
637+
staging (2 replicas, info logging), production (3 replicas, warn logging).
638+
All validated by Nickel contracts.
639+
* **Rolling deployment** — Replaces one replica at a time, waits for health
640+
checks, and automatically rolls back on failure.
641+
* **Image signature verification** — Before deploying to staging or
642+
production, verifies that container images are signed with cerro-torre.
643+
* **Self-validation** — The component typechecks itself before deployment.
644+
645+
See: `examples/container-deploy.k9.ncl` for the complete implementation.
646+
647+
=== Example: Config Validator as a Yard Component
648+
649+
Not every container-related component needs Hunt level. A configuration
650+
validator that checks deployment configs without executing anything is a
651+
perfect Yard-level component.
652+
653+
[source,nickel]
654+
----
655+
# Security: Yard level because this component:
656+
# - Evaluates Nickel contracts (typed validation)
657+
# - Produces validated JSON output
658+
# - Does NOT execute commands, access network, or write files
659+
security = {
660+
trust_level = 'Yard,
661+
allow_network = false,
662+
allow_filesystem_write = false,
663+
allow_subprocess = false,
664+
},
665+
----
666+
667+
This component can:
668+
669+
* Validate that container port mappings do not conflict
670+
* Check that resource limits are within acceptable ranges
671+
* Verify that environment variables match expected patterns
672+
* Export validated config as JSON for consumption by other tools
673+
674+
See: `examples/config.k9.ncl` for a Yard-level configuration example.
675+
676+
=== How k9-svc Wraps selur-compose
677+
678+
The typical workflow for a k9-svc container deployment:
679+
680+
[source]
681+
----
682+
1. Author writes .k9.ncl with Nickel-typed service definitions
683+
2. nickel typecheck validates the configuration (Yard-level eval)
684+
3. nickel export generates selur-compose.toml from the typed config
685+
4. The Hunt-level deploy script:
686+
a. Verifies component signature (Ed25519)
687+
b. Verifies container image signatures (cerro-torre)
688+
c. Runs selur-compose up with the generated config
689+
d. Monitors health checks
690+
e. Rolls back on failure
691+
----
692+
693+
The key insight is that steps 1-3 are **Yard-level** operations — they only
694+
evaluate Nickel expressions and produce data. Step 4 is a **Hunt-level**
695+
operation that requires the cryptographic handshake. This separation means
696+
you can validate configs without any security ceremony, but execution always
697+
requires authorization.
698+
699+
=== Anti-Pattern Warnings
700+
701+
Before wrapping something in k9-svc, read link:examples/NOT-a-good-fit.adoc[NOT-a-good-fit.adoc].
702+
703+
Common mistakes:
704+
705+
* **Do NOT** wrap plain config files in k9 pedigree. A `config.toml` is
706+
already Kennel-level by nature. Adding pedigree overhead provides zero
707+
security benefit.
708+
* **Do NOT** use k9-svc to manage library dependencies. Libraries are
709+
build-time artifacts, not deployment components. Use `Cargo.toml` or
710+
`mix.exs`.
711+
* **Do NOT** use Hunt level for read-only operations. If your component
712+
only reads data, use Kennel or Yard.
713+
* **Do NOT** wrap every directory in a2ml manifests. One at the repo root
714+
is sufficient for most projects.
715+
716+
See: link:examples/NOT-a-good-fit.adoc[Comparison, Deprecation Analysis, and Anti-Patterns]
717+
for the full analysis.
718+
554719
== License
555720

556-
K9 SVC is licensed under AGPL-3.0-or-later. See LICENSE for details.
721+
K9 SVC is licensed under PMPL-1.0-or-later. See LICENSE for details.

0 commit comments

Comments
 (0)