Skip to content

Updating & patching CodeNavi

How CodeNavi installs receive fixes and features — what ships, how big it is, how it is verified, and how to roll back. (Vendor release-side mechanics live in release-cut-runbook.md, internal.)

Connected update (the one-command path)

If the install host can reach the update channel, the bundled updater runs the whole delta flow — check, download only the changed layers, verify, apply — in one command. Nothing changes until you confirm, and your data/ (license, settings), your index, and your .env are never touched.

# Linux (x86-64) — from your install directory
./update.sh --check      # report what's new, apply nothing
./update.sh              # download the changed layers, then confirm before applying
./update.sh --yes        # ...or skip the confirm prompt
# Windows (Docker Desktop) — from %USERPROFILE%\codenavi
.\update.ps1 -Check      # report what's new, apply nothing
.\update.ps1             # download + confirm before applying
.\update.ps1 -Yes        # ...or skip the confirm prompt

An older release is refused. The updater compares the signed version of what the channel offers with the version you run, and will not install an older one — a valid signature on an old release is what a replayed or stale answer looks like. A rollback your CodeNavi contact asks for is the one exception: ./update.sh --allow-downgrade (Windows: .\update.ps1 -AllowDowngrade). To go back on your own, use the rollback tag instead (Rolling back).

running scripts is disabled on this system (Windows) — that is the built-in Windows PowerShell 5.1, whose default policy runs no scripts at all. Either run it the way the installer does, powershell -ExecutionPolicy Bypass -File .\update.ps1 -Yes (affects that one process only), or use PowerShell 7 (pwsh), which allows local scripts by default.

Under the hood this is the same signed, layer-verified delta described below; update.sh / update.ps1 just fetch the right package for your version from the channel first. Only the layers that changed are loaded into Docker or Podman: the layers your current image already holds are neither read again nor reloaded, so a code-only update stays small on disk and in time — Docker Desktop on Windows included. An engine that will not take such a partial load gets the full image instead, automatically. Air-gapped hosts (no outbound network) skip this and apply packages by hand, below.

The updater runs on the install host itself — Linux x86-64. The scripts use GNU coreutils (sha256sum, GNU find), so they do not run on macOS. CodeNavi's GPU indexing is x86-64 + NVIDIA anyway; setup.sh now refuses other architectures up front instead of failing later as a health-check timeout.

If an update is interrupted

An update is two steps: load the new image, then restart the container onto it. If the process dies in between (Ctrl-C, power cut, a failed docker compose up), the image on disk is new while the container still serves the old one.

Just re-run ./update.sh. It compares the running container's image against the one on disk, notices the half-applied update, and finishes it by restarting the container:

The codenavi:secure image on disk is NEWER than the running container.
A previous update loaded the image but never restarted. Finishing it now.

Nothing is re-downloaded, and your data is untouched. (Older updaters1 compared the tag instead of the running container, so this state reported "You're on the latest build. Nothing to update." while the old container kept serving — a security update could look applied when it wasn't. If you are on an older updater, force it with docker compose up -d --force-recreate.)

The three update sizes

CodeNavi is delivered as a signed Docker image plus install assets. Updates come in three shapes, chosen by what actually changed. None of them touch your data/ (license, settings, aliases), your index, or your .env.

Package Typical size Contains When
Assets refresh (…-assets.tar.gz) ~60 KB compose files, setup.sh, docs, plugin installer/compose/doc fixes, no server change
Delta update (…-update-<id>.tar.gz) ~3 MB only the image layers that changed + assets + apply-update.sh server code releases (the common case)
Full package (….tar.gz) ~10 GB everything: image, models, license, assets first install, dependency/model changes, broken delta chain

The economics: the image is ~14 GB uncompressed, but ~99.98 % of it (the embedding model, the ML runtime) almost never changes. The Docker image is built so all of that sits in stable layers and the application code is the last, ~3 MB layer — a code release only has to ship that.

Fresh installs work the same way. The image inside an install package is a base that changes only when the model or the ML runtime does. A connected install loads it, caches its layers, and then applies the current release as a small delta before starting the server, so a package downloaded today gives you today's build even if the base was built months ago. An air-gapped install has no update token, so it runs the base build and updates later by carrying a blob store across the gap. Set CODENAVI_NO_INSTALL_UPDATE=1 to skip that final step and stay on the base build.

Applying an assets refresh

tar xzf codenavi-<version>-assets.tar.gz -C <parent dir of your install>
cd <install dir> && docker compose up -d --force-recreate

Applying a delta update

mkdir cn-update && tar xzf codenavi-<version>-update-<id>.tar.gz --strip-components=1 -C cn-update
cd cn-update
sha256sum -c SHA256SUMS
./apply-update.sh
# then, from your INSTALL directory:
docker compose up -d --force-recreate
curl -s http://localhost:8765/health

What apply-update.sh does, step by step:

  1. Verifies the signed update manifest (packages since 2026-07-29 bundle bin/cosign, so this verification always runs — no system install needed; on older packages without it, the SHA256SUMS you just checked covers integrity). The manifest pins the image's layer chain, so everything later is checked against a signed expectation.
  2. Confirms your current image is the one this delta was built against — by comparing layer chains, not tags. If you're on a different version it refuses and tells you to ask for a matching delta or a full package.
  3. Reassembles the full image: unchanged layers are exported from the image already in your Docker daemon; changed layers come from the package. If the old image is gone from the daemon, point it at your kept original tarball: ./apply-update.sh --from-tarball <…-image.tar.gz>.
  4. Re-hashes every layer (they are content-addressed — the file name is the checksum) and cross-checks the image manifest against the signed manifest, so a corrupt or tampered package cannot assemble into a loadable image.
  5. Keeps a rollback tag (codenavi:rollback-<id>) pointing at your previous image, loads the new one, and verifies the loaded layer chain matches the signed manifest before declaring success.

Disk note: reassembly needs roughly 2× the uncompressed image size of scratch space. It defaults to a workdir next to the package (never /tmp, which is often RAM-backed); use --workdir /big/disk if needed.

Layer cache.2 Step 3 — the multi-minute docker save export — only happens on your first update. A successful apply leaves a content-addressed layer cache (default ./.layer-cache, ~1× image size on disk; --layer-cache DIR to relocate, --no-layer-cache to disable), and later updates serve the unchanged layers from it instead of round-tripping the image through the daemon. This is a speed cache, not a trust anchor: every entry is re-hashed against its digest before use (a corrupt or stale entry is just a cache miss that falls back to the export), and the assembled image is still verified blob-by-blob and chain-checked against the signed manifest exactly as before. The cache is pruned to the just-applied image after every successful update. Safe to delete at any time.

To get the speedup on your very next update (instead of the one after), prime the cache from your current image once: ./bin/apply-update.sh --prime-cache (pre-1.3.4 installs: ./apply-update.sh at the package root) — one docker-save export, no update applied, no package manifest needed. Measured on the r312→r317 delta: cold apply 1m45s, primed/warm apply ~35s.

Rolling back

docker tag codenavi:rollback-<id> codenavi:secure
docker compose up -d --force-recreate

Your data and index were never touched, so rollback is just the image swap above.

Applying a full package

Same flow as a first install: unpack and ./setup.sh --airgap (Windows: powershell -ExecutionPolicy Bypass -File .\setup.ps1). An existing install's .env and data/ are preserved (setup never overwrites them).

Practical rules

  • Keep your original package tarball. It is the rollback artifact and the fallback layer source for deltas (--from-tarball).
  • Updates are cumulative only in the sense that each delta targets one specific previous version. If you skip several releases, ask for a delta against your version or take a full package.
  • After any update finishing in --force-recreate, give the server ~1–2 minutes to reload the embedding model before heavy use (on ≤12 GB GPUs, don't trigger a reindex during startup).
  • Works on both Docker storage backends (classic graphdriver and the containerd image store) — the delta format deliberately uses only backend-invariant pieces.

  1. Vendor change reference CN-355. ↩

  2. Vendor change reference CN-196. ↩