Outpost software updates
The Arbitex Outpost supports two update methods:
-
Signed bundle update API (shipped in outpost-0034) — A built-in update manager that downloads, verifies, and stages update bundles via the outpost admin API. Bundles are Ed25519-signed tarballs. Recommended for connected deployments.
-
Docker image update — Pull and recreate the outpost container with a new image tag. Used for major upgrades, Kubernetes deployments, and environments where bundle-based updates are not configured.
Source: outpost/services/software_update.py, outpost/admin/routes.py
Signed bundle update API (outpost-0034)
Section titled “Signed bundle update API (outpost-0034)”Starting with outpost-0034, the outpost includes a SoftwareUpdateManager service that manages the full update lifecycle: version check → bundle download → signature verification → staging. Updates are applied by restarting the outpost process — there is no auto-apply.
Required environment variables
Section titled “Required environment variables”| Variable | Required | Description |
|---|---|---|
SOFTWARE_UPDATE_RELEASE_URL | Yes | URL to the JSON release manifest. When set, the update manager is initialized. |
SOFTWARE_UPDATE_SIGNING_KEY | Yes | Base64url-encoded 32-byte Ed25519 public key used to verify bundle signatures. Fail-closed: updates are rejected if this key is absent or the signature does not match. |
UPDATE_STAGE_DIR | No | Directory for staged bundle files (default: /tmp/outpost-update-stage). |
OUTPOST_VERSION | No | Current running version, for version comparison in the manifest check. |
If SOFTWARE_UPDATE_RELEASE_URL is not set, all update API endpoints return a 503 with "Software update not configured".
Update lifecycle
Section titled “Update lifecycle”The update process has three explicit steps, each triggered by an admin API call:
IDLE → CHECKING → AVAILABLE → DOWNLOADING → STAGED ↓ (restart) APPLIEDIf any step fails, the status transitions to ERROR. Re-trigger from the beginning (/check first).
Step 1 — Check for updates
Section titled “Step 1 — Check for updates”POST /admin/api/updates/checkAuthorization: Basic admin:<OUTPOST_EMERGENCY_ADMIN_KEY>Fetches the release manifest from SOFTWARE_UPDATE_RELEASE_URL. The manifest must include version, download_url, and signature_url. Compares the manifest version against OUTPOST_VERSION.
Response:
{ "status": "available", "current_version": "0.33.0", "latest_version": "0.34.0", "release_notes": "outpost-0034: signed bundle update API, policy simulator improvements", "download_progress": null, "staged_at": null, "staged_version": null, "staged_path": null, "signature_verified": null, "error": null}If already at the latest version, status is "idle".
Step 2 — Download and verify
Section titled “Step 2 — Download and verify”POST /admin/api/updates/downloadAuthorization: Basic admin:<OUTPOST_EMERGENCY_ADMIN_KEY>Downloads the bundle tarball and its Ed25519 signature file concurrently from the URLs in the manifest. Verifies the signature using the SOFTWARE_UPDATE_SIGNING_KEY public key.
Signature verification:
- Uses the
cryptographylibrary’sEd25519PublicKey.verify(). - Fail-closed: if
SOFTWARE_UPDATE_SIGNING_KEYis missing, the key is invalid, or the signature does not match, the download is rejected and status is set to"error". The bundle is never staged with a failed or missing signature.
Response on success:
{ "status": "staged", "current_version": "0.33.0", "latest_version": "0.34.0", "download_progress": 1.0, "staged_at": 1741824000.0, "staged_version": "0.34.0", "staged_path": "/tmp/outpost-update-stage/outpost-0.34.0.tar.gz", "signature_verified": true, "error": null}Response on signature failure:
{ "status": "error", "error": "Signature verification failed — bundle rejected (fail-closed)", "signature_verified": null}Step 3 — Check staged status
Section titled “Step 3 — Check staged status”GET /admin/api/updates/statusAuthorization: Basic admin:<OUTPOST_EMERGENCY_ADMIN_KEY>Returns the current update manager state. Use this to poll during a download or to confirm staging before applying.
| Field | Description |
|---|---|
status | idle / checking / available / downloading / staged / error |
current_version | Running outpost version |
latest_version | Version from the last manifest fetch |
release_notes | Release notes from the manifest |
download_progress | Float 0.0–1.0 during download, 1.0 when staged |
staged_at | UNIX timestamp when the bundle was staged |
staged_version | Version of the staged bundle |
staged_path | Filesystem path to the staged .tar.gz |
signature_verified | true if the staged bundle passed signature verification |
error | Error message if status == "error" |
Step 4 — Apply the update
Section titled “Step 4 — Apply the update”No auto-apply: once status == "staged" and signature_verified == true, apply the update by restarting the outpost process:
# Docker Composedocker compose -f docker-compose.outpost.yml restart outpost
# Kuberneteskubectl rollout restart deployment/arbitex-outpost -n arbitexOn startup, the outpost extracts the staged bundle from staged_path and loads the new binary. After restart, verify with GET /admin/api/updates/status — current_version should match staged_version and status should be "idle".
Air-gap update procedure (manual bundle placement)
Section titled “Air-gap update procedure (manual bundle placement)”In air-gap deployments (OUTPOST_AIRGAP=true) or networks where the outpost cannot reach the release manifest URL, place bundles manually:
-
Obtain the bundle from your Arbitex account team. The bundle package contains:
outpost-{version}.tar.gz— the update tarballoutpost-{version}.tar.gz.sig— the Ed25519 signature file
-
Copy the bundle to the stage directory:
Terminal window # Default stage dircp outpost-0.34.0.tar.gz /tmp/outpost-update-stage/cp outpost-0.34.0.tar.gz.sig /tmp/outpost-update-stage/ -
Trigger download (the download step also performs signature verification even for manually placed bundles, as long as the files exist at the manifest URLs or in the stage directory):
For pure air-gap with no manifest server, use the direct bundle injection endpoint if available, or restart the outpost with the
UPDATE_STAGE_DIRenvironment pointing to the bundle directory. The update manager will pick up the pre-placed bundle on startup. -
Verify signature before restart:
Terminal window # Manually verify using the same Ed25519 key# (requires cryptography package)python3 - <<'EOF'import base64from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKeykey_b64 = "<SOFTWARE_UPDATE_SIGNING_KEY>"raw_key = base64.b64decode(key_b64 + "==")public_key = Ed25519PublicKey.from_public_bytes(raw_key)bundle = open("outpost-0.34.0.tar.gz", "rb").read()sig = open("outpost-0.34.0.tar.gz.sig", "rb").read()public_key.verify(sig, bundle)print("Signature valid")EOF -
Restart the outpost to apply the staged bundle.
How the outpost detects updates
Section titled “How the outpost detects updates”The outpost sends a heartbeat to the Platform management plane every 120 seconds via mTLS. The heartbeat payload includes the current running version:
{ "version": "0.1.0", "uptime": 3600, "policy_version": "abc123", ...}The Platform heartbeat response includes a latest_version field:
{ "latest_version": "0.2.0"}When the running version differs from latest_version, the outpost logs a warning:
WARNING outpost.heartbeat: Outpost version outdated: running=0.1.0 latest=0.2.0 — update recommendedThe outpost admin status API also exposes this signal:
GET /admin/api/statusResponse:
{ "outpost_id": "op_01abc123", "policy_version": "abc123", "update_available": true, "latest_version": "0.2.0", "uptime_seconds": 7200, ...}update_available: true means the Platform has signaled that a newer version is available. The admin panel displays an update notification banner when this field is true.
Checking for updates
Section titled “Checking for updates”Admin panel
Section titled “Admin panel”Open the outpost admin panel at http://localhost:8301 (or your outpost admin URL). If an update is available, a notification banner appears at the top of the Status page:
Update available: Outpost 0.2.0 is available (running 0.1.0). See the update guide to apply.
Admin API
Section titled “Admin API”Poll the status endpoint directly:
curl -s \ -u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \ http://localhost:8301/admin/api/status \ | jq '{update_available, latest_version, policy_version}'Policy sync status and signature verification
Section titled “Policy sync status and signature verification”The sync-status endpoint shows the current policy bundle signature state alongside version information:
curl -s \ -u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \ http://localhost:8301/admin/api/sync-status \ | jq '{bundle_version, latest_version, bundle_signature_valid, bundle_verified_at}'| Field | Description |
|---|---|
bundle_version | Policy bundle version currently loaded |
latest_version | Latest outpost software version reported by Platform |
bundle_signature_valid | Whether the loaded policy bundle passed signature verification |
bundle_verified_at | UNIX timestamp of the most recent signature check |
bundle_signature_valid: true confirms the policy bundle was verified against the Platform’s signing key. This is separate from the software update — it verifies the policy configuration, not the container image.
Applying a software update
Section titled “Applying a software update”Standard update (Docker Compose)
Section titled “Standard update (Docker Compose)”The outpost runs as Docker Compose services. A software update involves pulling the new image and recreating the containers.
Step 1 — Back up the current configuration
cp .env .env.bakStep 2 — Pull the new image
docker compose -f docker-compose.outpost.yml pullThis pulls arbitex-outpost:latest (or the pinned tag in your compose file) without stopping the running containers.
Step 3 — Verify the image signature
Arbitex signs all release images with Docker Content Trust (DCT). Verify before applying:
# Enable content trust for this commandDOCKER_CONTENT_TRUST=1 docker pull arbitex/outpost:0.2.0If the image has not been signed or the signature does not match, Docker will refuse to pull it and print an error. Do not proceed if signature verification fails — contact Arbitex support.
For air-gapped deployments, see Signature verification in air-gap mode below.
Step 4 — Apply the update with a rolling restart
docker compose -f docker-compose.outpost.yml up -d --force-recreateThis recreates the containers using the newly pulled image. Existing connections to the proxy are gracefully terminated; new connections are handled by the updated container.
Step 5 — Verify the update
Wait 30 seconds for the outpost to fully initialize, then confirm the running version:
curl -s \ -u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \ http://localhost:8301/admin/api/status \ | jq '{update_available, latest_version}'Expected after a successful update:
{ "update_available": false, "latest_version": "0.2.0"}Also check the health endpoint:
curl -sf http://localhost:8300/health && echo "healthy"Kubernetes update
Section titled “Kubernetes update”If you run the outpost on Kubernetes via the Arbitex Helm chart:
Step 1 — Update the image tag in values
image: tag: "0.2.0"Step 2 — Apply the Helm upgrade
helm upgrade arbitex-outpost arbitex/outpost \ -f values-prod.yaml \ --namespace arbitexHelm performs a rolling update by default — old pods are kept running while new pods start and pass readiness checks.
Step 3 — Verify
kubectl rollout status deployment/arbitex-outpost -n arbitexkubectl exec -n arbitex deployment/arbitex-outpost -- \ curl -s -u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} http://localhost:8301/admin/api/status \ | jq .latest_versionAir-gap update sideloading
Section titled “Air-gap update sideloading”In air-gap deployments (OUTPOST_AIRGAP=true), the outpost does not connect to the Platform management plane and cannot receive heartbeat responses. Update detection and application must be done manually.
Obtaining the update package
Section titled “Obtaining the update package”Contact Arbitex support or your account team to receive the air-gap update bundle for the target version. The bundle contains:
arbitex-outpost-{version}.tar— the Docker image archivearbitex-outpost-{version}.sha256— SHA-256 checksum filearbitex-outpost-{version}.sig— Ed25519 image signature
Verifying the signature
Section titled “Verifying the signature”-
Import the Arbitex release signing public key (provided by your Arbitex account team):
Terminal window # Import the public keygpg --import arbitex-release-signing.pub -
Verify the signature on the image archive:
Terminal window gpg --verify arbitex-outpost-0.2.0.sig arbitex-outpost-0.2.0.tarA successful verification prints:
gpg: Good signature from "Arbitex Release Signing Key <releases@arbitex.ai>"Do not proceed if the signature is invalid or the key cannot be verified.
-
Verify the checksum:
Terminal window sha256sum -c arbitex-outpost-0.2.0.sha256Expected output:
arbitex-outpost-0.2.0.tar: OK
Loading the image
Section titled “Loading the image”docker load < arbitex-outpost-0.2.0.tarUpdating the policy bundle (air-gap policy sideload)
Section titled “Updating the policy bundle (air-gap policy sideload)”Air-gap outposts load the policy bundle from a local volume rather than syncing from the Platform. To update the policy bundle:
-
Obtain the new
policy_bundle.jsonfrom your Arbitex account team (delivered as a signed archive using the same GPG key). -
Verify the signature on the bundle archive.
-
Place the new bundle at the configured path:
Terminal window # Default path (override with AIRGAP_POLICY_PATH)cp policy_bundle.json /opt/arbitex/policies/policy_bundle.json -
Restart the outpost to load the new bundle:
Terminal window docker compose -f docker-compose.outpost.yml restart -
Verify the bundle loaded:
Terminal window curl -s \-u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \http://localhost:8301/admin/api/sync-status \| jq '{bundle_version, bundle_signature_valid}'
Applying the software update (air-gap)
Section titled “Applying the software update (air-gap)”-
Tag the loaded image to match the compose file’s image reference:
Terminal window docker tag arbitex-outpost:0.2.0 arbitex-outpost:latest -
Recreate the containers:
Terminal window docker compose -f docker-compose.outpost.yml up -d --force-recreate -
Verify as in the standard update steps above.
Signature verification in air-gap mode
Section titled “Signature verification in air-gap mode”The outpost verifies policy bundle signatures using a trusted Platform public key embedded at build time. In connected mode, the Platform signs bundles and the outpost verifies on each sync. In air-gap mode, the same verification runs when a new bundle is loaded.
bundle_signature_valid in the sync-status response reflects the most recent verification result. If this field is false after loading a new bundle, the bundle was not signed with the expected key — do not use it. Contact Arbitex support.
Rollback procedure
Section titled “Rollback procedure”Docker Compose rollback
Section titled “Docker Compose rollback”If the updated outpost is unhealthy or causing errors, roll back by specifying the previous image tag.
Step 1 — Edit the compose file to pin the previous version
services: outpost: image: arbitex/outpost:0.1.0 # previous versionStep 2 — Pull and recreate
docker compose -f docker-compose.outpost.yml pulldocker compose -f docker-compose.outpost.yml up -d --force-recreateStep 3 — Verify
curl -s \ -u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \ http://localhost:8301/admin/api/status \ | jq .update_available# Expected: true (since the rolled-back version is older than latest)After rollback, update_available will be true again — that is expected. Investigate the issue before re-applying the update.
Kubernetes rollback
Section titled “Kubernetes rollback”kubectl rollout undo deployment/arbitex-outpost -n arbitexkubectl rollout status deployment/arbitex-outpost -n arbitexThis rolls back to the previous ReplicaSet. Helm history is also available:
helm history arbitex-outpost -n arbitexhelm rollback arbitex-outpost <revision> -n arbitexUpdate checklist
Section titled “Update checklist”Before applying any update:
- Read the release notes for the target version (available at
https://docs.arbitex.ai/changelog/outpost). - Verify the image signature before loading.
- Back up
.envand any local configuration. - Test the update in a non-production outpost first if possible.
- Confirm
bundle_signature_valid: trueafter restart. - Confirm
update_available: falseafter restart (connected mode) or check the version field directly (air-gap mode). - Run a test request through the proxy to confirm DLP and policy evaluation are functioning.
See also
Section titled “See also”- Outpost deployment guide — initial installation and environment configuration
- Outpost health monitoring — heartbeat monitoring, cert expiry alerts, sync status
- Air-gap deployment — full air-gap configuration reference
- Outpost admin API — complete admin API reference for status, sync-status, and airgap-config endpoints