Skip to content

Versioning & releases

Taranac is shipped as a small set of containers that run as one stack. To keep upgrades predictable, the project versions software along deployment boundaries, not along source directories. As an operator you deal with one number — the platform version — and everything you deploy carries it, including the remote collectors and captive portals that attach from other networks. This page explains what that version covers, where every version string you can see comes from, what actually guarantees compatibility, and how the product tells you a newer release exists.

The Platform line carries one Git tag and the database schema migration head. taranac-mfa keeps its own number for the contract it holds with the mobile app — a number that has not moved since the two lines were separated.

There are two version lines. They are not expected to match each other, and only one of them is something you deploy.

LineWhat it coversWhy these ship together
PlatformBackend, admin UI (frontend), captive portal, the public edge proxy, the report renderer, the domain sidecar, the push-MFA service image, and the TACACS+, RADIUS and NAC daemonsThey roll out as one stack from one docker-compose, under one Git tag — every image in it is pulled as <registry>/<service>:${TARANAC_VERSION}. The daemons consume configuration generated by the backend, and the captive portal is configured by the backend — so the config format is contract-bound to the backend version and cannot be released separately.
taranac-mfa (push-MFA)The number the push-authentication service reports on its own /healthz and to the mobile appIt tracks the HTTP API contract with the mobile app, not a deployment: in the shipped stack the service runs from an image tagged with the platform version like everything else. It moves only when that contract does, and has stayed at 1.0.0 across every platform release since the lines were separated.

The platform line is at 1.2.8, released 2026-08-25 — twelve releases since 1.1.0 on 2026-07-17. What each one changed is recorded in the project’s own changelog, published as the GitHub release notes — this page describes the policy, not the history. Both lines follow a MAJOR.MINOR.PATCH (semantic-versioning) shape, but see What versions don’t tell you below — for an operator the contracts matter more than the number.

The platform version has a single source of truth: the VERSION file shipped in the deployment bundle (deploy/dist/VERSION), the one number a release raises by hand. Everything else is derived from it — it is baked into the container images at build time (APP_VERSION for the backend, VITE_APP_VERSION for the admin UI, COLLECTOR_VERSION for the collector), and the backend reads that baked file in preference to any APP_VERSION in its environment. So there is no display version to set: the image is authoritative for the version it reports.

The one version knob an operator has is TARANAC_VERSION in .env, and it is the image tag the stack pulls — bumped for you by ./taranac update, never a value you keep in step by hand.

A single platform version pins the whole AAA/NAC stack together: the admin UI you log into, the AAA engines that answer device requests, the captive-portal frontend, and — crucially — the database schema that backs all of it. When you move from one platform version to another, all of these move as a set. You should not run a backend of one version against a frontend or a daemon configuration of another; that combination is untested and unsupported.

Remote modules belong to that set too, even though they run on other hosts. A standalone NCM collector or a DMZ captive portal runs the release its core runs — which is not the newest published release while you are upgrading hosts one at a time. Each is moved by its own tool (./collector-join.sh --update, or re-attaching the portal), and since 1.2.7 ./taranac update on a module host deliberately refreshes only the bundle’s files and leaves the module’s container alone. The module learns which version to move to from the core itself: since 1.2.3 the core returns its own release in an X-Taranac-Core-Version header on every authenticated answer, so a box in a DMZ that cannot reach a release feed can always reach the one thing it needs to.

The platform stores its configuration and logs in a relational database. Each platform release that changes the schema ships one or more migrations — ordered, numbered scripts that bring an existing database forward to the new layout.

Migrations are applied automatically: on container startup the backend runs the pending migrations up to the latest (“head”) before the application begins serving. In practice this means an upgrade is pull the new images, restart the stack — the schema is brought current for you. You do not run migrations by hand in normal operation. Migrations are forward-only and idempotent, so a jump across several releases lands every intervening migration in one pass.

On a cluster the same step is leader-gated: only the node whose local database is the primary migrates, and a replica — whose local database is read-only — waits for the leader’s schema head to arrive by streaming replication before its API starts serving. That is why the upgrade order on a cluster is replicas first, primary last, and why a replica that reports itself as waiting for a schema head is telling you the primary has not been upgraded yet. See Upgrading a cluster, which the primary has enforced with a refusal since 1.2.8.

The migration cadence follows the release cadence: a release that needs a schema change includes the migration for it, so the applied migration on a healthy system corresponds to the platform version that introduced it.

Reading the version and migration in the admin UI

Section titled “Reading the version and migration in the admin UI”

The dashboard’s System widget is the operator-facing place to read what is actually running. It shows:

FieldMeaning
VersionThe running platform version (the backend’s reported version).
MigrationThe currently applied database schema revision (the Alembic migration “head”), shown as a short identifier.
UptimeHow long the backend process has been running.
Timezone / clockThe system timezone and a live server clock.
LatestOnly when update checks are enabled and you may view them: the newest release the signed feed reported, whether it differs from the one you are running, and a link to its release notes.

Taranac dashboard with the System widget showing version, uptime, the applied migration and the update-check row The dashboard System widget reports the running platform version and the applied schema migration. (Captured on 1.2.0 — the fields are current; the version shown is not.)

The platform version is also shown in the admin-UI footer (as v<version>) and on error pages, so it is visible even when you are not on the dashboard.

For automation and health checks, the backend exposes HTTP probes:

GET /healthz → { "status": "ok" } # liveness only
GET /ready → { "status": "healthy",
"version": "1.2.8",
"uptime_seconds": 1234,
"checks": { "database": "ok",
"master_key": "loaded" } }

/healthz is a bare liveness probe and returns no version. The version, uptime and component health live on /ready — use that endpoint if you want to read the running version programmatically. status is healthy only when the database is reachable and the master key is loaded; otherwise it reports degraded.

The 1.1.0 fix was precisely about these surfaces disagreeing, so it is worth knowing what each one reads:

SurfaceWhere the number comes from
Dashboard System widget (GET /dashboard/system-info)The backend’s own installed package metadata, falling back to the image-baked version. Kept in lockstep with VERSION by the release tooling.
GET /readyThe image-baked version.
API metadata (OpenAPI / Swagger version)The image-baked version.
Admin-UI footer (v<version>), login screen, error pagesVITE_APP_VERSION, baked into the frontend bundle at build.
Backup archive manifest.json (app_version)The image-baked version, recorded when the archive is written — a restore checks it against the running one.
Collector / captive-portal heartbeatsThe core’s version, returned to the module in X-Taranac-Core-Version. Attached only after the caller’s signature verifies, so an unauthenticated caller learns nothing.
./taranac version on the hostThe bundle’s own VERSION file on disk — not .env. It also reads the TARANAC_VERSION image pin, and reports both when they differ.
taranac-mfa’s /healthz and its answer to the mobile appThe push-MFA service’s own __version__.

./taranac version reading two numbers is deliberate. The bundle’s VERSION says what scripts and compose files are on disk; TARANAC_VERSION says which image tag the stack pulls, and an upgrade writes the files first and the pin last — so the two agree only when the last upgrade ran to the end. Where they differ you get both numbers and the command that finishes the job. (Since 1.2.6 that comparison also strips surrounding quotes and trailing carriage returns, so an .env edited on Windows no longer offers you an update to the release you are already running.) The full behaviour is in Backups & upgrades.

Taranac can ask taranac.pro whether a newer release has been published. It is off out of the box and stays off until you enable it: nothing leaves the installation until you do. The check is pull-only, and a failed check is never an error — an air-gapped installation degrades to an “unreachable” status rather than an alarm.

What the answer is: a static JSON manifest, signed offline with an Ed25519 key at release time and verified on the appliance against a shipped trusted-key map. No signing key is ever online, and a manifest that is malformed, tampered with, signed by an unknown key, or answering for the wrong edition is discarded — the previous known-good result is kept.

What the request carries — stated plainly, because “it only checks for updates” is not a complete answer:

  • Your edition (Community or Pro) and the version you are on, as query parameters.
  • A Pro installation additionally sends its installation identifier as ?iid= — that is how a Pro feed can answer for a specific install.
  • A Community installation sends nothing identifying beyond the above.
  • A User-Agent of taranac/<version>.

The manifest also carries a min_supported field. It is transported and available on the API, but nothing is enforced by it and it is not currently shown in the UI — treat it as advisory, not as a gate.

Cadence, endpoint and the permission that governs who may see the result are in the settings reference (update_feed.*). The “Check now” button is an explicit per-click pull and works even with the scheduled check turned off.

A version number does not, by itself, guarantee that two components will interoperate. Real compatibility lives in contracts:

  • Backend ↔ TACACS+/RADIUS/NAC daemons — the format of the configuration the backend generates for the daemons.
  • Backend ↔ taranac-mfa — the HTTP API between them.
  • Core ↔ standalone collector — the snapshot identity: the scrub rules, content-hash and secret-fingerprint algorithms by which a captured configuration is identified.

Bumping a number does not protect against a broken contract, and matching numbers across the two version lines do not imply compatibility. This is why the platform components are versioned and released as one unit: it removes the question of “which backend goes with which daemon” entirely. For push-MFA, keep the mobile app on a release that shares the same API contract.

The collector contract is the one place where the product versions a contract explicitly, and it is worth understanding because you can see it fail. It carries its own token, separate from any release number and deliberately opaque — it has no ordering, so any difference is a mismatch, a downgrade included. On a mismatch the core fail-closes: it dispatches nothing to that collector and ingests nothing from it, because a skewed collector would compute different hashes for the same device configuration and quietly corrupt the version history with phantom changes and deduplicated real ones. An idle collector is the safer failure. It resumes on its own once it is moved onto its core’s release.

When you upgrade the platform:

  1. Back up first (configuration and database) — see Backups & upgrades.
  2. Run ./taranac update. It replaces the bundle’s files, pins the new TARANAC_VERSION, pulls the images and restarts the stack. Pending migrations apply automatically on startup. On a cluster, replicas first and the primary last.
  3. Confirm the result in the System widget: the Version should reflect the new release and Migration should advance to the new head. /ready should report healthy.
  4. Check ./taranac version agrees with itself. If it warns that the bundle on disk and the .env image pin differ, the previous update stopped part-way — it names the command that finishes it.
  5. Move any standalone collectors and DMZ captive portals onto the same release, in the same window. They follow the core, not the newest publication.

The push-MFA service needs no separate upgrade step — its image ships in the stack under the same tag. Its own number is the API contract it holds with the mobile app; what matters there is that the app in use speaks the same contract.

AspectPlatform linetaranac-mfa line
CoversBackend, admin UI, captive portal, edge proxy, report renderer, domain sidecar, TACACS+/RADIUS/NAC daemons, database schema — and the release a remote collector or portal must runThe push-MFA service’s API contract with the mobile app
Released asOne Git tag, one stack, one image tag for every service in itNot released separately — the service ships in the stack under the platform tag
Where shown to operatorsSystem widget, UI footer, /ready, ./taranac version, backup manifestThe service’s own /healthz and its answer to the mobile app
Compatibility guaranteed byGenerated-config contract; shipped as one set; an explicit, fail-closed contract token for standalone collectorsHTTP API contract with the mobile app
Current value1.2.8 (2026-08-25)1.0.0
  • Backups & upgrades — the recommended backup-then-upgrade procedure, the update CLI and the cluster upgrade order.
  • Architecture — how the platform components and daemons fit together.
  • Settings reference — configuration surface of the platform, including the update_feed.* keys.
  • CLI reference./taranac version, ./taranac update and ./collector-join.sh --update.
  • Release notes — the changelog of every published version.
  • Taranac on GitHub — the deployment bundle, releases and tags.