Build and publish the device packages
For guided installation, use the managed installer.
Build and publish iris-arm64.tar, iris-amd64.tar and iris-xr.rpm, each beside its provenance manifest.
Skip this page if you onboard only through Guest Shell. On Catalyst 9000 series switches the agent runs in Guest Shell, and the server rebuilds that bundle every time it starts. The IOx app is the alternative on switches with app-hosting storage.
Before you start
- Finish Check the host before you install.
- Install the build tools: the two pinned
aria2cbinaries,ioxclient, arm64 emulation and the appmgr builder. See Download the tools that build device packages. - Have the two public root files ready. See Create the two offline signing keys.
- Point
IRIS_INSTRUCTION_ROOTS_DIRat the reviewed directory holding exactly two distinct public.pubfiles. Every package embeds these public roots. - Have the server healthy, with no onboarding job fetching a package right now.
Where to build and publish
| Layout | Build here | Publish here |
|---|---|---|
| Single Docker host | Server host checkout | That Compose project's server container, /srv/artifacts |
| Separate Docker hosts | Server host checkout, never the Console host | Server container's /srv/artifacts bind mount |
| Kubernetes | Approved amd64 Docker build host, outside the pods | Server pod's PVC at /data/artifacts, never the Console pod |
Docker on one host
Build in the server host's checkout once the stack is healthy:
# Build and stage both packages during server bring-up (recommended).
tools/provision-iox-packages.sh
# Industrial Ethernet switches with app hosting: arm64 package served as iris-arm64.tar
tools/stage-iox-package.sh --arch arm64
# Catalyst 9000 series with app-hosting storage: amd64 package served as iris-amd64.tar
tools/stage-iox-package.sh --arch amd64
tools/stage-iox-package.sh rebuilds one package. On first use it downloads
Cisco's pinned ioxclient release and checks it against
tools/ioxclient.sha256. Set IOXCLIENT to use an installation you already
have. For a full rebuild, or to publish iris-xr.rpm, follow
Build the packages.
The default browser identity is encrypted in tls/console-fallback.pem.age and
reused on restart. See
Set up certificates and tokens.
Docker on separate hosts
Run these commands on the server host to build both IOx wrappers and the XR RPM and publish them:
set -a
. server/server.env
set +a
export IRIS_INSTRUCTION_ROOTS_DIR="$HOME/iris-roots"
tools/provision-iox-packages.sh
tools/build-xr-package.sh --out device/xr/out
sudo install -o 10001 -g 10001 -m 444 device/xr/out/iris-xr.rpm \
"$IRIS_ARTIFACTS_HOST_DIR/.iris-xr.rpm.next"
sudo install -o 10001 -g 10001 -m 444 device/xr/out/iris-xr.rpm.manifest \
"$IRIS_ARTIFACTS_HOST_DIR/.iris-xr.rpm.manifest.next"
sudo rm -f "$IRIS_ARTIFACTS_HOST_DIR/iris-xr.rpm.manifest"
sudo mv "$IRIS_ARTIFACTS_HOST_DIR/.iris-xr.rpm.next" \
"$IRIS_ARTIFACTS_HOST_DIR/iris-xr.rpm"
sudo mv "$IRIS_ARTIFACTS_HOST_DIR/.iris-xr.rpm.manifest.next" \
"$IRIS_ARTIFACTS_HOST_DIR/iris-xr.rpm.manifest"
Sourcing server/server.env gives these helpers the same container name and
artifact directory as Compose. For one wrapper only, follow
Build the packages.
Kubernetes
Build outside the pods, then copy each package and its .manifest into the
server pod's volume. Follow
Build the packages and use the
Kubernetes publishing commands.
Put the two approved public root files under /data/config/instr/roots.d. See
Create the two offline signing keys.
Build the packages
Use this procedure for every layout. Only the destination changes.
1. Select both device architectures and prepare ioxclient, in the same shell:
export IRIS_DEVICE_PLATFORMS=linux/amd64,linux/arm64
tools/get-ioxclient.sh
IRIS includes the tested clients at bin/aria2c for amd64 and
deliverables/aria2c-aarch64 for ARM64. The package builders verify them
against tools/aria2c.sha256. get-ioxclient.sh prepares Cisco's packaging
tool, not the device clients.
2. Set the build environment and build the ARM wrapper into a private directory:
export IRIS_INSTRUCTION_ROOTS_DIR="$HOME/iris-roots"
export ARIA2C_BIN_AMD64="$PWD/bin/aria2c"
export ARIA2C_BIN_ARM64="$PWD/deliverables/aria2c-aarch64"
IRIS_ARM_BUILD_DIR="$(mktemp -d)"
export IRIS_DEVICE_IMAGE_OCI="$IRIS_ARM_BUILD_DIR/iris-device.oci.tar"
tools/stage-iox-package.sh --arch arm64 \
--artifacts-dir "$IRIS_ARM_BUILD_DIR"
The command exits zero and writes iris-arm64.tar and its .manifest into
$IRIS_ARM_BUILD_DIR. Keep the image archive and its provenance in approved
build storage.
This step rebuilds the canonical device image with --pull, so it always
starts from a fresh base. Set IRIS_NO_PULL=1 to keep the cached base instead,
for an A/B build that isolates an unrelated source change.
3. Check the wrapper against its manifest:
PYTHONPATH="$PWD/server" python3 - "$IRIS_ARM_BUILD_DIR" <<'PY'
import json, os, sys
from setup_status import package_readiness
name = "iris-arm64.tar"
result = package_readiness(os.path.join(sys.argv[1], name), name,
"iox", "linux/arm64", "rebuild ARM64 IOx")
print(json.dumps(result))
sys.exit(0 if result["state"] == "ok" else 1)
PY
sha256sum "$IRIS_ARM_BUILD_DIR/iris-arm64.tar" \
"$IRIS_ARM_BUILD_DIR/iris-arm64.tar.manifest"
The state is ok. On failure, read
If a build or publish fails.
4. Build the other two packages in the same shell, with the same roots,
platform list and IRIS_DEVICE_IMAGE_OCI:
tools/stage-iox-package.sh --arch amd64 --artifacts-dir "$IRIS_ARM_BUILD_DIR"
tools/build-xr-package.sh --out "$IRIS_ARM_BUILD_DIR"
5. Publish each package with its matching .manifest, using the section for
your layout and the filename substituted.
Publish to either Docker layout
Publish when the stack is healthy and no onboarding is fetching this package.
Resolve the server container, keeping any -p or --project-name option
you use. Run only the matching line:
# One host, from that host's checkout:
IRIS_CONTAINER="$(docker compose --env-file server/.env -f server/docker-compose.yml ps -q iris)"
# Separate hosts, from the SERVER host's checkout:
IRIS_CONTAINER="$(docker compose --env-file server/server.env -f server/docker-compose.server.yml ps -q iris)"
Then confirm the container and the bind mount, and copy the pair in:
test -n "$IRIS_CONTAINER"
docker inspect "$IRIS_CONTAINER" --format '{{range .Mounts}}{{if eq .Destination "/srv/artifacts"}}{{.Source}}{{end}}{{end}}'
docker cp "$IRIS_ARM_BUILD_DIR/iris-arm64.tar" "$IRIS_CONTAINER:/srv/artifacts/.iris-arm64.tar.tmp"
docker cp "$IRIS_ARM_BUILD_DIR/iris-arm64.tar.manifest" "$IRIS_CONTAINER:/srv/artifacts/.iris-arm64.tar.manifest.tmp"
docker exec "$IRIS_CONTAINER" sh -ec '
cd /srv/artifacts
test -r .iris-arm64.tar.tmp && test -r .iris-arm64.tar.manifest.tmp
rm -f iris-arm64.tar.manifest
mv -f .iris-arm64.tar.tmp iris-arm64.tar
mv -f .iris-arm64.tar.manifest.tmp iris-arm64.tar.manifest
sha256sum iris-arm64.tar iris-arm64.tar.manifest'
The printed hashes equal the build-host hashes. Uid 10001 must be able to read the files and write the destination directory. If the readability check fails, fix the mode of the two source files on the build host and copy them again.
Warning
Do not run chown inside the container, and do not widen the modes of
state or secret files.
Publish to Kubernetes
Apply your manifests and wait for the server Deployment. The example uses
namespace iris.
kubectl -n iris rollout status deployment/iris-seed-server
kubectl -n iris get pods -l app.kubernetes.io/name=iris-seed-server
IRIS_SERVER_POD="$(kubectl -n iris get pods -l app.kubernetes.io/name=iris-seed-server -o jsonpath='{.items[0].metadata.name}')"
test -n "$IRIS_SERVER_POD"
kubectl -n iris exec "$IRIS_SERVER_POD" -c iris -- test -w /data/artifacts
kubectl -n iris cp --no-preserve "$IRIS_ARM_BUILD_DIR/iris-arm64.tar" "$IRIS_SERVER_POD:/data/artifacts/.iris-arm64.tar.tmp" -c iris
kubectl -n iris cp --no-preserve "$IRIS_ARM_BUILD_DIR/iris-arm64.tar.manifest" "$IRIS_SERVER_POD:/data/artifacts/.iris-arm64.tar.manifest.tmp" -c iris
kubectl -n iris exec "$IRIS_SERVER_POD" -c iris -- sh -ec '
cd /data/artifacts
rm -f iris-arm64.tar.manifest
mv -f .iris-arm64.tar.tmp iris-arm64.tar
mv -f .iris-arm64.tar.manifest.tmp iris-arm64.tar.manifest
sha256sum iris-arm64.tar iris-arm64.tar.manifest'
Match both hashes against the build host.
kubectl cp --no-preserve
keeps host ownership off the non-root pod, and needs tar in the container.
Verify
Run tools/check-package-freshness.sh on the server host, or open
Settings → Device packages in the Console. Both print one row per package
family, and the Console prints the full build command for any family that is
missing, stale or unverifiable.
A row is ready when the readable wrapper bytes match the adjacent provenance
manifest, so keep each .manifest beside its package.
The Docker helper also reads mounted packages and the distributed certificate as the server identity (uid/gid 10001), even when invoked as host root. A stopped server, incorrect identity or unreadable file fails verification. Package readiness alone does not establish signing or installation readiness.
The same status compares the certificate the live services present with the public copy onboarding distributes.
Warning
A mismatch blocks new onboarding. Rebuilding the packages does not fix it.
When to rebuild the packages
Shared agent source, device image or client changes require fresh Guest Shell, both IOx and IOS-XR outputs. Build images without replacing running containers:
docker compose -f server/docker-compose.yml build --pull
Use Build the packages for private output. Follow Deploy a new release, which removes and re-onboards device agents after deployment replacement. On Kubernetes, build the replacement's server image and publish packages there. Verify readiness on the new deployment before onboarding.
After a client-pin change, rebuild both images and every device package. Copy them to the replacement. Agents must be file-identical across packages.
Keep the same release and public roots when generating or signing its packages.
Warning
A certificate rotation needs no rebuild. Re-onboard the affected devices so each one receives the current public certificate. See Rotate credentials and certificates.
If native signing changes the wrapper bytes, publish the signed output with a manifest recording that output's SHA-256.
If a build or publish fails
| Symptom | Likely cause | What to do |
|---|---|---|
bin/aria2c is not the amd64 client, or a client does not match tools/aria2c.sha256 |
A committed client was replaced or deleted by hand | Restore it with git checkout -- bin/aria2c deliverables/aria2c-x86_64 deliverables/aria2c-aarch64. The build scripts check every client against the checksum file and stop on a mismatch. |
exec format error, a missing handler, or a request for a binfmt digest |
The amd64 build host has no arm64 emulation | Set emulation up as Download the tools that build device packages describes. Do not pull an unpinned privileged image. |
| No OCI exporter, or the ARM manifest is missing | The Buildx builder cannot write the archive, or only amd64 was built | Inspect the builder and set IRIS_DEVICE_PLATFORMS=linux/amd64,linux/arm64. Do not reuse an amd64-only archive. |
| The builder refuses to replace the existing image archive | The archive on disk came from different source | Build into the private build directory and keep the previous archive. |
| The two public roots are not available | The reviewed roots directory is missing or holds the wrong files | Get the two approved public roots the deployment already uses. Do not generate replacements. |
| The artifact directory is not writable, or the package never appears in the Console | The build wrote straight into the server-owned directory | Build into the private output directory, then publish to the server mount or volume. |
| The package row is still not ready | One of the two files, a hash, the platform or the provenance is wrong | Check both published files and their hashes, check the platform, and read the reported provenance reason. |