Offline Installation (Air-Gapped Servers)
This guide is for customers whose target server has no internet access and cannot reach the Python Package Index directly. The idea: build a self-contained bundle on an internet-connected machine, transfer it to the target server, and install from local files only.
When to Use This Method
Use this approach when:
- The target server lives in an isolated network segment.
- Outbound HTTPS to
pypi.orgis blocked by a firewall or proxy policy. - You need a reproducible, ship-once-install-anywhere artifact for a customer handover.
For internet-connected hosts, prefer the PyPI install or the Git-based install — they are simpler.
Overview
graph LR
A[Build Host with Internet] --> B[Download Wheels] --> C[Create tar.gz] --> D[Transfer to Target] --> E[pip install --no-index]
Two machines are involved:
| Role | Needs Internet | Purpose |
|---|---|---|
| Build host | Yes | Downloads all wheels and builds the bundle |
| Target server | No | Receives the bundle and installs offline via pip |
Warning
The build host should match the target's Python version and Linux platform. Wheels are Python-version- and platform-specific — a bundle built on macOS for Python 3.12 will not install on a Linux server running Python 3.11.
Build the Bundle
On the build host, clone the repository and run the helper script. It
lives in tools/ and wraps pip download plus a bit of packaging:
git clone https://github.com/kuhn-ruess/cmdbsyncer
cd cmdbsyncer
./tools/build_offline_bundle.sh --include-syncer --include-enterprise \
--python-version 3.11 \
--platform manylinux2014_x86_64
The bundle always ships the base, extras and ansible Python dependencies plus the default Ansible playbook collection — there are no toggles for these any more. The script produces:
offline_bundle/— directory withpackages/, the threerequirements*.txtfiles, the bundledansible/playbook tree, aninstall.shand aREADME.txtoffline_bundle.tar.gz— the same folder as a single archive ready to ship
Script Options
| Flag | Purpose |
|---|---|
--include-syncer |
Also download the cmdbsyncer package from PyPI into the bundle |
--include-enterprise |
Also download the cmdbsyncer-enterprise package from PyPI |
--syncer-version VER |
Pin cmdbsyncer to exactly this version (e.g. 4.1.0.dev3). Required for pre-releases. |
--enterprise-version VER |
Same idea for cmdbsyncer-enterprise |
--python-version VER |
Target Python version, e.g. 3.11 — must match the target server |
--platform TAG |
Target platform tag, e.g. manylinux2014_x86_64 for typical Linux |
--output-dir DIR |
Override output directory (default: offline_bundle) |
--no-archive |
Skip the tar.gz step; only produce the directory |
Note
When --platform is set, the script enforces --only-binary=:all:
so pip ships wheels instead of source distributions. Pure-Python
packages work fine; packages with C extensions must have a matching
wheel on PyPI. If a wheel is missing, pip will abort with a clear
error.
Bundling pre-releases
Pre-release builds (.devN, aN, bN, rcN) are not picked up
by an unpinned cmdbsyncer request — pip ignores them by default.
Always pass --syncer-version (and --enterprise-version if you
bundle Enterprise too) for a pre-release build:
./tools/build_offline_bundle.sh \
--include-syncer --syncer-version 4.1.0.dev3 \
--include-enterprise --enterprise-version 0.3.9.dev1 \
--python-version 3.11 --platform manylinux2014_x86_64
Without the version flags the bundle ships the latest stable
release. Either way the generated install.sh pins to the same
exact wheel, so the customer's pip never has to make a resolution
decision in the field.
Transfer the Bundle
Copy offline_bundle.tar.gz to the target server using whatever
out-of-band channel is available — SCP over a jump host, a USB drive,
an internal artifact repository, a support ticket attachment:
scp offline_bundle.tar.gz user@target-server:/tmp/
Install on the Target Server
On the target server, extract the archive, create a virtual environment, and run the bundled installer:
cd /opt
tar -xzf /tmp/offline_bundle.tar.gz
python3.11 -m venv venv
source venv/bin/activate
/opt/offline_bundle/install.sh
The bundled install.sh installs every requirement file from the
packages/ directory and copies the bundled Ansible playbook collection
to /opt/cmdbsyncer/ansible.
Customising the install
The installer respects a few environment variables — set them before
running install.sh:
| Variable | Effect |
|---|---|
ANSIBLE_TARGET=/path |
Where the playbook collection lands (default: /opt/cmdbsyncer/ansible). An existing directory is replaced. |
SKIP_ANSIBLE=1 |
Skip the playbook copy entirely (you already manage the playbooks elsewhere) |
The pip and Ansible steps run independently — a failure in one is reported but never silently swallows the other.
After install, point cmdbsyncer at the playbook directory by exporting
CMDBSYNCER_ANSIBLE_DIR=<path> in the service environment, or by adding
ANSIBLE_DIR=<path> to local_config.py.
Next Steps
If you built the bundle with --include-syncer, the cmdbsyncer
console script is now on your PATH — the application behaves exactly
like a regular PyPI install. Continue with
Installation from PyPI to
initialize the application (cmdbsyncer sys self_configure), point it
at MongoDB, and set up a WSGI server.
If you built the bundle without --include-syncer, the Syncer source
code is not part of the bundle — ship the repository separately (e.g.
as a tarball or Git archive) and follow the
Installation from Code guide from
the ./cmdbsyncer sys self_configure step onwards, using the venv you
just populated.
Upgrades
For every new release, rebuild the bundle on the internet-connected
host, transfer the new archive, and re-run install.sh. pip will pick
up newer wheels from packages/ and upgrade in place. After the
install, re-run self_configure so new default values are seeded:
cd /opt/cmdbsyncer
source venv/bin/activate
./cmdbsyncer sys self_configure
Troubleshooting
ERROR: Could not find a version that satisfies the requirement ...
The wheel for that package is missing from packages/. Verify that
--python-version and --platform on the build host match the target
server. Rebuild with matching values.
ERROR: ... is not a supported wheel on this platform
Same root cause — the bundle was built for a different interpreter or
CPU architecture. A common trap is building on macOS (macosx_* tags)
for a Linux target. Always pass --platform manylinux2014_x86_64 for
typical Linux servers.
Dependency conflict warnings from a previously installed
cmdbsyncer
An older Syncer version in the venv pins older dependency versions. Run
pip uninstall cmdbsyncer first, or upgrade the Syncer itself to the
matching release.
Packages with C extensions fail to build
Some integrations (notably python-ldap) need system headers
(libldap2-dev, libsasl2-dev, ...) if no wheel is available. Install
them via the distribution package manager on the target server, or make
sure the build host has matching wheels available on PyPI.