Air-Gap Deployment
CT-Ops is designed to work in fully air-gapped environments — no internet access is required for any core feature, including agent registration, metrics collection, alerting, and agent binary distribution.
What "Air-Gapped" Means Here
- All Docker images are bundled into a single tarball for transfer
- The web app serves agent binaries from internal storage — agents never download from the internet
- Licence validation uses a bundled public key (no phone-home)
- All TLS certificates are self-signed or from your internal CA
- No external CDN dependencies — all static assets are self-hosted
Bundling the Images
On a machine with internet access, run the bundle script to pull all images and save them to a tarball:
# Clone the repo (or download just the scripts)
git clone https://github.com/carrtech-dev/ct-ops
cd ct-ops
# Bundle all images into a single tarball
bash deploy/scripts/airgap-bundle.sh
# Output: ct-ops-bundle-<version>.tar.gz
The tarball contains:
webimageingestimagetimescale/timescaledbimagedocker-compose.single.ymlstart.sh.env.example
Transferring the Bundle
# Copy to the target server (adjust as appropriate)
scp ct-ops-bundle-*.tar.gz ops@air-gapped-server:/opt/ct-ops/
Or use a USB drive, secure FTP, or any other approved transfer mechanism.
Loading Images on the Target Server
cd /opt/ct-ops
tar -xzf ct-ops-bundle-*.tar.gz
docker load < images.tar
Starting the Stack
# First run: creates .env
./start.sh
nano .env # configure BETTER_AUTH_URL, passwords, etc.
# Second run: starts everything
./start.sh
No internet access is used during startup. The web container runs migrations from the bundled Drizzle schema — no external migration service is contacted.
Agent Distribution
Agent binaries are hosted by the web app, not downloaded from GitHub. The ingest service serves a manifest of available agent versions, and the web app serves the binaries from its AGENT_DIST_DIR volume.
To add a new agent binary to an air-gapped deployment:
Build the agent on a machine with internet access:
make agent-all # builds for linux/amd64, linux/arm64, darwin/amd64Transfer the binary to the server:
scp dist/agent-linux-amd64 ops@server:/var/lib/ct-ops/agent-dist/Update the agent version manifest in the web UI: Settings → Agents → Update Manifest
Agents poll the ingest service for the minimum required version. When a newer binary is available, they download it from the web app — entirely within your network.
Installing Agents on Isolated Hosts
Hosts that cannot reach the CT-Ops server at install time (jump-host-only networks, change-controlled servers, or hosts behind a one-way data diode) can be enrolled using a portable install bundle generated by the CT-Ops UI.
From Settings → Agent Enrolment, an org_admin or super_admin clicks Download Install Bundle, picks the target OS / arch, and downloads a zip containing:
- The agent binary for that platform
install.sh(Linux / macOS) orinstall.ps1(Windows)agent.tomlpre-populated with this server's ingest addressSHA256SUMSand aREADME.md
The bundle can be transferred by any approved mechanism (USB, jump host, managed file share) and installed with a single command — no outbound connectivity to the CT-Ops server is required during install. A single-use, short-expiry enrolment token can be embedded in the bundle, or left out so the operator supplies it via CT_OPS_ORG_TOKEN at install time.
See Offline Agent Install Bundle for the full walkthrough.
Updates
To update CT-Ops in an air-gapped environment, repeat the bundle and transfer process with the new version:
# On internet-connected machine
CT_OPS_VERSION=v0.4.0 bash deploy/scripts/airgap-bundle.sh
# Transfer the new tarball
scp ct-ops-bundle-v0.4.0.tar.gz ops@server:/opt/ct-ops/
# On the target server
docker load < images.tar
docker compose -f docker-compose.single.yml pull # no-op: images already loaded
docker compose -f docker-compose.single.yml up -d # restarts with new images
Licence Validation
CT-Ops validates enterprise licences offline using a signed JWT verified against a public key that is bundled with the binary. No network request is made. To update a licence, paste the new licence key into Settings → Licence.