- Update registry URLs from ghcr.io/nvidia/nemoclaw to ghcr.io/nvidia/openshell - Rename NEMOCLAW_* environment variables to OPENSHELL_* - Update CLI command references in documentation - Update PyPI package name references - Update GitHub repository URLs - Rename .agents/skills/nemoclaw-cli directory to openshell-cli
28 KiB
Container Management
This document describes how the project's container images are built, organized, and deployed. It covers the Dockerfiles, build automation, Helm chart configuration, and CI/CD integration.
Directory Structure
deploy/
docker/
.dockerignore
Dockerfile.sandbox # Sandbox container (runs agent code in isolation)
Dockerfile.server # Gateway container (orchestration / control plane)
Dockerfile.cluster # Airgapped k3s cluster with Helm charts and manifests
Dockerfile.ci # CI runner image with pre-installed toolchain
Dockerfile.python-wheels # Multi-arch Linux wheel builder for the Python CLI package
Dockerfile.python-wheels-macos # macOS arm64 wheel builder (cross-compilation via osxcross)
cross-build.sh # Shared Rust cross-compilation helpers for multi-arch builds
cluster-entrypoint.sh # Entrypoint script for DNS proxy and registry config in Docker
cluster-healthcheck.sh # Docker HEALTHCHECK script for cluster readiness
.build/ # Generated artifacts (charts/*.tgz)
helm/
navigator/ # Helm chart for the gateway
Chart.yaml
values.yaml
templates/
_helpers.tpl
statefulset.yaml
service.yaml
serviceaccount.yaml
role.yaml
rolebinding.yaml
kube/
manifests/ # Kubernetes manifests for k3s auto-deploy
navigator-helmchart.yaml
agent-sandbox.yaml # Agent Sandbox CRD controller RBAC
tasks/
docker.toml # Docker image build tasks
cluster.toml # Cluster bootstrap and deploy tasks
helm.toml # Helm lint task
rust.toml # Rust build/lint/format tasks
ci.toml # Public quality tasks and CI entrypoint
test.toml # Test tasks (Rust + Python)
python.toml # Python build/lint/format tasks
publish.toml # Release publishing tasks
version.toml # Git-derived version management
scripts/
docker-build-component.sh # Generic Docker image builder
docker-build-cluster.sh # Cluster image builder (packages Helm charts first)
docker-publish-multiarch.sh # Multi-arch build and push (registry or ECR)
cluster-bootstrap.sh # Full cluster bootstrap (registry + push + deploy)
cluster-push-component.sh # Tag and push a single component to the local registry
cluster-deploy-fast.sh # Incremental deploy from local Git changes
Container Images
The project produces three runtime container images and two build-only wheel images.
Sandbox Image (openshell/sandbox)
The sandbox container runs inside each sandbox pod. It contains the sandbox supervisor binary, Python runtime, AI agent tooling, and a virtual environment for the Python SDK.
Build stages (5 stages in deploy/docker/Dockerfile.sandbox):
- rust-builder -- Cross-compiles the
navigator-sandboxbinary from Rust. Usesdeploy/docker/cross-build.shfor multi-arch support (amd64/arm64). Build profile is controlled by theRUST_BUILD_PROFILEarg (default:debug). - base -- Python 3.12 slim with system dependencies (
iproute2for network namespace management,dnsutils,curl, etc.) and two users:supervisor(privileged) andsandbox(restricted). - builder -- Installs Python dependencies via
uvinto/app/.venv. Includes the SDK dependencies (cloudpickle,grpcio,protobuf). - coding-agents -- Installs AI agent CLIs: Claude (via native installer), OpenCode (
opencode-ainpm package), and Codex (@openai/codexnpm package). Requires Node.js and npm. - final -- Combines the Rust binary, Python venv, SDK source, and coding agents. Creates
/var/navigatorfor policy files and/sandboxowned by thesandboxuser. Entrypoint isnavigator-sandbox.
Key details:
- Multi-user isolation:
supervisor(runs the sandbox supervisor) andsandbox(runs the restricted agent process). - Policy files are mounted at
/var/navigator/policy.rego(rules) and/var/navigator/data.yaml(data) when running in file-based policy mode. - The Python SDK is copied directly into the venv's site-packages at
/app/.venv/lib/python3.12/site-packages/navigator/.
Gateway Image (openshell/server)
The gateway container runs the control plane / orchestration service.
Build stages (2 stages in deploy/docker/Dockerfile.server):
- builder -- Two-pass Rust compilation with dependency caching:
- First pass copies only
Cargo.toml/Cargo.lockfiles and creates dummy source files (fn main() {}/ emptylib.rs) to build dependencies in isolation. This layer is cached unless dependency manifests change. - Second pass copies real source, touches source files and
.protodefinitions to force rebuilding, and compiles in release mode. - Uses
deploy/docker/cross-build.shfor multi-arch cross-compilation. - Proto files are copied and
build.rsis touched to ensure proto code regeneration when the cache mount retains staleOUT_DIRartifacts.
- First pass copies only
- runtime -- Debian bookworm-slim (pinned to a specific point release) with
ca-certificates. Runs as non-root usernavigator(created withuseradd --create-home). SQLx migrations are copied to/build/crates/navigator-server/migrations(the path expected bysqlxat build time).
Key details:
- Exposes port 8080 (gRPC + HTTP multiplexed, mTLS required).
- No Docker HEALTHCHECK -- health checks are handled by Kubernetes liveness/readiness probes (
tcpSocketon the gRPC port). - Entrypoint:
navigator-server, default args:--port 8080.
Cluster Image (openshell/cluster)
A k3s image with bundled Helm charts and Kubernetes manifests for single-container deployment. Component images (sandbox, gateway) are pulled at runtime from the distribution registry -- they are not bundled as tarballs in this image.
Defined in deploy/docker/Dockerfile.cluster.
Base image: rancher/k3s:v1.35.2-k3s1 (configurable via K3S_VERSION build arg).
Layers added:
- Custom entrypoint:
cluster-entrypoint.shat/usr/local/bin/. - Healthcheck script:
cluster-healthcheck.shat/usr/local/bin/. - Packaged Helm charts:
deploy/docker/.build/charts/*.tgzat/var/lib/rancher/k3s/server/static/charts/. - Kubernetes manifests:
deploy/kube/manifests/*.yamlat/opt/navigator/manifests/.
Bundled manifests:
navigator-helmchart.yaml-- HelmChart CR that auto-deploys the gateway chart from the k3s static file server.agent-sandbox.yaml-- Agent Sandbox CRD controller namespace, service account, and RBAC bindings.
HEALTHCHECK: --interval=5s --timeout=5s --start-period=20s --retries=60, runs /usr/local/bin/cluster-healthcheck.sh.
Runtime image pulling: Registry credentials are generated by the entrypoint script at container start and written to /etc/rancher/k3s/registries.yaml. The bootstrap code (navigator-bootstrap) passes registry host, endpoint, and credentials as environment variables on the container.
Python Wheel Images (build-only)
Two Dockerfiles produce Python wheels for the CLI package distribution. These are not deployed as running containers.
Dockerfile.python-wheels-- Builds Linux amd64/arm64 wheels using Maturin with a two-pass Rust build (dependency prebuild + final wheel build), BuildKit cache mounts for cargo registry/git/target, sccache (backed by memcached whenSCCACHE_MEMCACHED_ENDPOINTbuild arg is provided), andcross-build.shfor conditional cross-toolchain installation. The final build step patches workspace version inside the container layer fromOPENSHELL_CARGO_VERSION(computed before Docker build), preserving cacheable dependency layers and avoiding dirty working-tree edits. Output stage isscratchwith only the.whlfiles.Dockerfile.python-wheels-macos-- Builds macOS arm64 wheels using osxcross (cross-compiling from Linux) with the same two-pass dependency caching pattern and cargo cache mounts. Version injection uses the same in-container workspace-version patch fromOPENSHELL_CARGO_VERSION, avoiding host-side file edits that break Docker layer caching. Usescrazymax/osxcross:latestas the cross-toolchain source. TheOSXCROSS_IMAGEbuild arg allows using a mirrored registry image instead of Docker Hub.
CI Runner Image (navigator-ci)
A pre-built Ubuntu 24.04 image for CI pipeline jobs, defined in deploy/docker/Dockerfile.ci.
Pre-installed tools:
| Tool | Purpose |
|---|---|
| Docker CLI + buildx plugin | DinD-based image build/publish jobs |
| AWS CLI v2 | ECR authentication and image publishing |
| kubectl, helm, protoc | Kubernetes operations, chart packaging, proto compilation |
| mise | Task runner with Rust and Python toolchains |
| uv | Python package management (installed from Astral's installer to avoid GitHub API rate limits) |
| sccache | Rust compilation cache (amd64 only; skipped on arm64) |
| socat | Docker socket forwarding in sandbox e2e tests |
The build context must include tasks/ because the Dockerfile copies mise task includes from that directory (mise.toml + tasks/*.toml).
Cross-Compilation Support
All Rust-based Dockerfiles (sandbox, gateway) use deploy/docker/cross-build.sh for multi-architecture builds. This script:
- Detects whether the build is cross-platform by comparing
TARGETARCHandBUILDARCH(set bydocker buildx). - Maps Docker arch names to Rust target triples (
arm64->aarch64-unknown-linux-gnu,amd64->x86_64-unknown-linux-gnu). - Installs the gcc cross-linker and target libc for the target architecture (no-op for native builds).
- Provides
cargo_cross_build()which setsCC,CXX, and linker environment variables and passes the correct--targetflag. - Provides
cross_output_dir()to locate the compiled binary in the correct target-specific output directory.
This enables the FROM --platform=$BUILDPLATFORM pattern: Rust compilation runs natively on the build host for speed, and only the final runtime stage runs on the target platform.
Entrypoint Script
deploy/docker/cluster-entrypoint.sh runs before k3s starts inside the cluster container. It performs four tasks.
DNS proxy setup
On Docker custom networks, /etc/resolv.conf contains 127.0.0.11 (Docker's embedded DNS). k3s detects this loopback address and falls back to 8.8.8.8, which does not work reliably on Docker Desktop (Mac/Windows) due to external UDP limitations.
The entrypoint solves this with an iptables-based DNS proxy:
- Discovers Docker's real DNS listener ports from the
DOCKER_OUTPUTiptables chain (Docker DNAT rules that redirect port 53 to random high ports on127.0.0.11). - Gets the container's
eth0IP as a routable address. - Adds DNAT rules in
PREROUTINGto forward DNS traffic from k3s pod network namespaces through the container'seth0IP to Docker's DNS. - Writes a custom resolv.conf at
/etc/rancher/k3s/resolv.confpointing to the container IP. - Passes
--resolv-conf=/etc/rancher/k3s/resolv.confto k3s.
If iptables detection fails, falls back to writing 8.8.8.8 / 8.8.4.4 as nameservers.
Registry configuration
Writes /etc/rancher/k3s/registries.yaml from environment variables passed by the bootstrap code:
| Variable | Purpose |
|---|---|
REGISTRY_HOST |
Registry hostname for the mirror entry |
REGISTRY_ENDPOINT |
Endpoint URL (defaults to REGISTRY_HOST) |
REGISTRY_INSECURE |
Use HTTP instead of HTTPS (parsed: true/1/yes/on) |
REGISTRY_USERNAME |
Auth username (optional) |
REGISTRY_PASSWORD |
Auth password (optional) |
Manifest injection
Copies bundled manifests from /opt/navigator/manifests/ to /var/lib/rancher/k3s/server/manifests/. This is necessary because the persistent volume mount on /var/lib/rancher/k3s overwrites any files baked into that path at image build time.
Image configuration overrides
Modifies the HelmChart manifest at /var/lib/rancher/k3s/server/manifests/navigator-helmchart.yaml based on environment variables:
| Variable | Effect |
|---|---|
IMAGE_REPO_BASE |
Rewrites repository: and sandboxImage: to use the specified base path |
PUSH_IMAGE_REFS |
Parses comma-separated image refs and rewrites exact server and sandbox references (matching on path component /server:, /sandbox:) |
IMAGE_TAG |
Replaces :latest tags with the specified tag (handles both quoted and unquoted tag: latest formats) |
IMAGE_PULL_POLICY |
Replaces pullPolicy: Always with the specified policy (e.g., IfNotPresent) |
SSH_GATEWAY_HOST / SSH_GATEWAY_PORT |
Replaces __SSH_GATEWAY_HOST__ and __SSH_GATEWAY_PORT__ placeholders; clears to defaults if unset |
Healthcheck Script
deploy/docker/cluster-healthcheck.sh validates cluster readiness through a series of sequential checks:
- Kubernetes API --
kubectl get --raw='/readyz'. - OpenShell StatefulSet -- Checks that
statefulset/navigatorin namespacenavigatorexists and has 1 ready replica. - TLS secrets -- Verifies that
navigator-server-tlsandnavigator-client-tlssecrets exist in thenavigatornamespace (created by the bootstrap crate before the StatefulSet starts).
Helm Chart
The Helm chart at deploy/helm/navigator/ deploys the gateway to Kubernetes as a StatefulSet.
Chart Metadata
| Field | Value |
|---|---|
| Name | navigator |
| Type | application |
| Version | 0.1.0 |
| appVersion | 0.1.0 |
Key Configuration (values.yaml)
replicaCount: 1
image:
repository: ghcr.io/nvidia/openshell/server
pullPolicy: Always
tag: "latest"
server:
logLevel: info
sandboxNamespace: navigator
dbUrl: "sqlite:/var/navigator/navigator.db"
sandboxImage: "ghcr.io/nvidia/openshell/sandbox:latest"
grpcEndpoint: "https://navigator.navigator.svc.cluster.local:8080"
sshGatewayHost: "" # Public host for SSH proxy CONNECT (default: 127.0.0.1)
sshGatewayPort: 0 # Public port for SSH proxy CONNECT (default: 8080)
tls:
certSecretName: navigator-server-tls
clientCaSecretName: navigator-server-client-ca
clientTlsSecretName: navigator-client-tls
service:
type: NodePort
port: 8080
nodePort: 30051
Deployment Architecture
The chart deploys a StatefulSet (not a Deployment) with a volumeClaimTemplate for persistent storage:
- PVC:
navigator-data,ReadWriteOnce, 1Gi, mounted at/var/navigator. - Security context: non-root (UID 1000),
fsGroup: 1000, all capabilities dropped, no privilege escalation. - Probes:
tcpSocketon thegrpcport (8080) for both liveness and readiness. Defaults are tuned for faster local rollouts:- liveness: initial delay 2s, period 5s, timeout 1s, failure threshold 3
- readiness: initial delay 1s, period 2s, timeout 1s, failure threshold 3 TCP probes are used because the server terminates TLS directly.
- Termination grace:
terminationGracePeriodSecondsis configurable via chart values (default 5s) to reduce restart latency during iterative deploys. - Service: NodePort exposing port 8080, with nodePort 30051.
Server TLS Configuration
The StatefulSet always mounts TLS secrets as volumes and sets environment variables for the server to use rustls with mTLS:
- Volumes:
tls-cert(fromnavigator-server-tlssecret, mounted at/etc/navigator-tls/server/) andtls-client-ca(fromnavigator-server-client-casecret, mounted at/etc/navigator-tls/client-ca/). - Environment:
OPENSHELL_TLS_CERT,OPENSHELL_TLS_KEY,OPENSHELL_TLS_CLIENT_CApointing to the mounted files. - Client TLS secret name:
OPENSHELL_CLIENT_TLS_SECRET_NAMEset to theclientTlsSecretNamevalue, used by the server to inject TLS volume mounts into sandbox pod specs. - gRPC endpoint:
https://navigator.navigator.svc.cluster.local:8080so sandbox pods connect over mTLS.
TLS certificates are generated at cluster bootstrap time by the navigator-bootstrap crate using rcgen, not by a Helm Job. The bootstrap reconciles three K8s secrets before the Helm release is installed:
- On first deploy, a full PKI hierarchy (CA, server cert, client cert) is generated and applied.
- On redeploy, existing secrets are loaded and reused if they contain valid PEM data. If secrets are missing, incomplete, or malformed, a fresh PKI is generated and applied.
- Certificate validity uses
rcgendefaults (effectively never expires), which is appropriate for an internal dev-cluster PKI where certs are ephemeral to the cluster's lifetime. - If PKI rotation occurs on a running cluster, the navigator workload (StatefulSet or Deployment) is automatically restarted and the rollout must complete before CLI-side credentials are updated. A failed rollout aborts the deploy.
The fast deploy script (cluster-deploy-fast.sh) always sets --set-string server.grpcEndpoint=https://... explicitly to enforce the HTTPS invariant, since the chart always terminates mTLS (there is no server.tls.enabled toggle).
Server Environment Variables (from StatefulSet)
| Environment Variable | Source | Description |
|---|---|---|
OPENSHELL_SANDBOX_NAMESPACE |
server.sandboxNamespace |
Kubernetes namespace for sandbox pods |
OPENSHELL_SANDBOX_IMAGE |
server.sandboxImage |
Container image for sandbox pods |
OPENSHELL_GRPC_ENDPOINT |
server.grpcEndpoint |
gRPC callback endpoint (reachable from pods) |
OPENSHELL_SSH_GATEWAY_HOST |
server.sshGatewayHost |
Public SSH gateway hostname (conditional) |
OPENSHELL_SSH_GATEWAY_PORT |
server.sshGatewayPort |
Public SSH gateway port (conditional) |
RBAC
The chart creates a Role and RoleBinding granting the gateway's ServiceAccount permissions to manage agents.x-k8s.io/sandboxes resources (CRUD + watch) and read events in the release namespace.
Build Tasks (mise)
All builds use mise tasks defined in tasks/*.toml (included from mise.toml).
Docker Image Tasks
| Task | Description |
|---|---|
mise run docker:build |
Build all runtime images (sandbox, gateway, cluster) |
mise run docker:build:sandbox |
Build sandbox image |
mise run docker:build:server |
Build gateway image |
mise run docker:build:cluster |
Build k3s cluster image (packages Helm charts first) |
mise run docker:build:ci |
Build CI runner image |
mise run docker:build:cluster:multiarch |
Build multi-arch cluster image and push to a registry |
mise run docker:publish:cluster:multiarch |
Build and publish multi-arch cluster image to ECR |
Cluster Lifecycle Tasks
| Task | Description |
|---|---|
mise run cluster |
Bootstrap or incremental deploy: creates cluster if needed, rebuilds changed components |
Other Tasks
| Task | Description |
|---|---|
mise run cluster:sandbox |
Run sandbox container interactively (builds image first) |
mise run helm:lint |
Lint the Helm chart |
How cluster Works (Incremental Deploy)
tasks/scripts/cluster-deploy-fast.sh supports two modes:
Auto mode (no arguments): Detects changed files from Git (unstaged, staged, and untracked), fingerprints the relevant local changes for each component, and rebuilds only components whose fingerprint changed since the last successful deploy.
| Changed Path | Triggers |
|---|---|
Cargo.toml, Cargo.lock, proto/*, deploy/docker/cross-build.sh |
Gateway + sandbox rebuild |
crates/navigator-core/*, crates/navigator-providers/* |
Gateway + sandbox rebuild |
crates/navigator-router/* |
Gateway rebuild |
crates/navigator-server/*, deploy/docker/Dockerfile.server |
Gateway rebuild |
crates/navigator-sandbox/*, deploy/docker/sandbox/*, python/*, pyproject.toml, uv.lock, crates/navigator-sandbox/data/sandbox-policy.rego |
Sandbox rebuild |
deploy/helm/navigator/* |
Helm upgrade |
Explicit target mode (arguments: server, sandbox, chart, all): Rebuilds only the specified components.
Auto mode persists the last deployed fingerprints in .cache/cluster-deploy-fast.state (or $DEPLOY_FAST_STATE_FILE). Re-running mise run cluster without new local changes prints No new local changes since last deploy. and skips rebuild/upgrade work.
After building, the script:
- Tags images with the
IMAGE_REPO_BASEprefix and pushes to the local registry. - Detects if the sandbox image content-addressable ID changed; if so, evicts the stale copy from k3s's containerd store via
crictl rmiso new sandbox pods pull the updated image. - Runs
helm upgradeif chart changes were detected (orFORCE_HELM_UPGRADE=1). - Restarts the gateway StatefulSet (or Deployment, if present) and waits for rollout completion.
- On success, updates the local deploy fingerprint state file for the next incremental deploy.
How mise run cluster Bootstrap Works
tasks/scripts/cluster-bootstrap.sh performs a full cluster bootstrap for local development:
- Resolves the local registry address (defaults to
127.0.0.1:5000/navigator). In CI, uses$CI_REGISTRY_IMAGE. - Ensures a local Docker registry container (
navigator-local-registry) is running on port 5000 (creates one if needed). - Pushes prebuilt local component images (server, sandbox) to the local registry via
cluster-push-component.sh. - Runs
nav gateway start --name <CLUSTER_NAME> --update-kube-configto create or update the cluster container.
Environment Variables
| Variable | Default | Description |
|---|---|---|
IMAGE_TAG |
dev |
Tag for built images |
RUST_BUILD_PROFILE |
debug |
debug or release for sandbox builds |
K3S_VERSION |
v1.35.2-k3s1 |
k3s version for cluster image (optional override; default in Dockerfile.cluster) |
| CLUSTER_NAME | basename of $PWD | Name for local cluster deployment |
| DOCKER_PLATFORM | (unset) | Target platform for multi-arch builds (e.g., linux/amd64) |
| DOCKER_BUILDER | (auto-selected) | Buildx builder name |
| IMAGE_REPO_BASE | 127.0.0.1:5000/navigator | Image repository base for local registry pushes |
| DEPLOY_FAST_STATE_FILE | .cache/cluster-deploy-fast.state | Location of local incremental deploy fingerprint state |
Build Caching
Container builds use Docker BuildKit with local cache directories:
tasks/scripts/docker-build-component.shstores per-component caches in.cache/buildkit/<component>.tasks/scripts/docker-build-cluster.shstores the cluster image cache in.cache/buildkit/cluster.mise run python:build:multiarchstores per-platform wheel caches in.cache/buildkit/python-wheels/<platform>for local builds when using adocker-containerbuildx driver.- Rust-heavy Dockerfiles use BuildKit cache mounts for cargo registry, cargo target, and sccache local disk directories. Cargo target cache mounts are keyed by image name,
TARGETARCH, and a computed scope hash derived fromCargo.lockplus a Rust toolchain hint, withsharing=lockedto prevent concurrent cache corruption in parallel CI builds. This reduces reuse of staletarget/artifacts across dependency or toolchain changes while preserving incremental rebuilds within a compatible scope. sccache uses memcached in CI (SCCACHE_MEMCACHED_ENDPOINT) and falls back to the local disk cache mount for local dev builds, providing a second layer of caching at the compilation unit level. - When the active buildx driver is
docker(notdocker-container), local cache import/export flags are skipped automatically because the docker driver cannot export local caches. In CI, cache export is also skipped. - For local single-arch builds, the scripts auto-select a builder with the native
dockerdriver (matching the active Docker context) so images land directly in the Docker image store without slow tarball export.
CI Caching
In CI pipelines:
- Remote BuildKit daemons (
buildkit-amd64andbuildkit-arm64) are used as persistent builders viadriver: remote. Their built-in layer cache persists across builds, so no external cache (registry-backed or otherwise) is needed in CI. - Rust lint/test jobs cache
target/with keys derived fromCargo.lockand Rust task config files, scoped per runner architecture. sccache uses a shared memcached backend (SCCACHE_MEMCACHED_ENDPOINT) instead of local disk. - CI sets
CARGO_INCREMENTAL=0to favor deterministic clean builds over incremental metadata churn. - Publish jobs mirror
crazymax/osxcross:latestinto$CI_REGISTRY_IMAGE/third_party/osxcross:latest(when missing) and setOSXCROSS_IMAGEso macOS wheel Docker builds consume the mirrored image instead of pulling from Docker Hub on each run. - The sandbox e2e test job tags and pushes component images to the GitLab project registry (
$CI_REGISTRY_IMAGE) and configures cluster bootstrap to pull from that remote registry with CI credentials.
Multi-Arch Publishing
tasks/scripts/docker-publish-multiarch.sh builds and pushes all images for multiple architectures.
Two modes:
| Mode | Registry | Notes |
|---|---|---|
registry |
DOCKER_REGISTRY env var |
Images named navigator-<component> |
ecr |
AWS ECR (account/region configurable) | Images named <component> (no prefix), --provenance=false --sbom=false |
Process:
- Builds and pushes sandbox and gateway images as multi-arch manifests using cross-compilation.
- Packages the Helm chart.
- Builds and pushes the multi-arch cluster image.
- Applies additional tags (
:latestifTAG_LATEST=true, plus anyEXTRA_DOCKER_TAGS) by copying manifests withdocker buildx imagetools create --prefer-index=false.
Default platforms: linux/amd64,linux/arm64 (overridable via DOCKER_PLATFORMS).
Deployment Flows
Local Development
# Bootstrap or incremental deploy (creates cluster if needed, rebuilds changed components)
mise run cluster
# Run sandbox container interactively (for testing sandbox code)
mise run sandbox
Multi-Arch Publishing
# Push to a generic Docker registry
DOCKER_REGISTRY=ghcr.io/myorg mise run docker:build:cluster:multiarch
# Push to ECR
mise run docker:publish:cluster:multiarch
# Main branch publish (dev + latest + version tags, cluster image + Python wheels)
mise run publish:main
GitHub Actions stages Python wheels in S3 before final publication to Artifactory:
- Wheels are uploaded to
s3://navigator-pypi-artifacts/navigator/<wheel-version>/. - A follow-up job on the
nvrunner lists that version prefix, downloads the wheels, and publishes them to Artifactory. - Container publish jobs compute the same Cargo version once and pass it through
Docker builds so
navigator-serverreports the packaged artifact version at runtime. - Published images keep the floating
latesttag and also receive an explicit version tag for the same manifest.
Auto-Deployed Components in Cluster
When the cluster container starts, k3s automatically deploys these HelmChart CRs from /var/lib/rancher/k3s/server/manifests/:
- OpenShell (from
navigator-0.1.0.tgzin the static charts directory) -- deployed intonavigatornamespace. The HelmChart CR'svaluesContentconfigures image references, SSH gateway settings, and TLS options. These values are rewritten by the entrypoint script based on environment variables from the bootstrap code.
Implementation References
deploy/docker/Dockerfile.sandbox-- Sandbox image (5-stage multi-arch build)deploy/docker/Dockerfile.server-- Gateway image (2-stage with dependency caching)deploy/docker/Dockerfile.cluster-- Cluster image (k3s base + charts + manifests)deploy/docker/Dockerfile.ci-- CI runner image (Ubuntu + full toolchain)deploy/docker/Dockerfile.python-wheels-- Linux wheel builderdeploy/docker/Dockerfile.python-wheels-macos-- macOS wheel builderdeploy/docker/cross-build.sh-- Shared Rust cross-compilation helpersdeploy/docker/cluster-entrypoint.sh-- Cluster container entrypointdeploy/docker/cluster-healthcheck.sh-- Cluster health check scriptdeploy/helm/navigator/-- Helm chart directorydeploy/kube/manifests/-- Auto-deployed Kubernetes manifeststasks/docker.toml-- Docker build task definitionstasks/cluster.toml-- Cluster lifecycle task definitionstasks/scripts/docker-build-component.sh-- Generic component image buildertasks/scripts/docker-build-cluster.sh-- Cluster image buildertasks/scripts/docker-publish-multiarch.sh-- Multi-arch publish scripttasks/scripts/cluster-bootstrap.sh-- Full local cluster bootstraptasks/scripts/cluster-deploy-fast.sh-- Incremental deploy scripttasks/scripts/cluster-push-component.sh-- Single component push to registry