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, GNUfind), so they do not run on macOS. CodeNavi's GPU indexing is x86-64 + NVIDIA anyway;setup.shnow 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:
- 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, theSHA256SUMSyou just checked covers integrity). The manifest pins the image's layer chain, so everything later is checked against a signed expectation. - 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.
- 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>. - 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.
- 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.