Stage Bundles For Air-Gapped Deployment
on this page
Use this guide when you want to prepare a run on one machine, transfer the result to an isolated environment, and apply it there without re-reading the original playbook or re-fetching actions.
Prerequisites
- A working
preflightbinary on the staging machine - A working
preflightbinary on each offline target that will runpreflight apply --bundle - A playbook that passes
preflight validate - Any referenced plugin executables discoverable during staging and built for the destination OS and architecture
- No tasks that would require embedding decrypted secret values into the bundle
1. Prepare Encrypted Secrets For Target-Side Apply
Skip this section if the staged plan does not reference secret:<name> values.
If the staged plan references secrets, the bundle includes the encrypted secret files. The offline machine still needs an identity that can decrypt those files when it runs preflight apply --bundle.
Use this pattern when the target should decrypt the bundle locally:
-
Keep or create a developer identity on the staging machine:
bashpreflight secret identity generate --out .age/keys.txt -
Generate a separate identity on the target machine:
bashpreflight secret identity generate --out .age/target-keys.txt -
On the target, print the target’s public recipient:
bashpreflight secret identity recipient .age/target-keys.txt -
Copy only the public recipient string back to the project and add it beside the developer recipient in
preflight.yml:yamlsecrets: identity: ".age/keys.txt" recipients: - "age1developerrecipient..." - "age1targetrecipient..." entries: autologin-password: file: "secrets/autologin-password.age" -
Re-encrypt the configured secrets to the updated recipient list:
bashpreflight secret rekey
Adding a recipient to preflight.yml does not change existing encrypted files by itself. Run preflight secret rekey after recipient changes so the staged encrypted secret payloads can be decrypted by the new target identity.
2. Stage The Bundles
Declare each offline host’s destination platform in preflight.yml. The
transport may describe how the host is normally managed; staging does not
connect through it when platform is present:
inventory:
hosts:
- name: lobby-pc-01
address: 192.168.1.10
transport: winrm
platform:
os: windows
arch: amd64Run:
preflight stage playbooks/lobby.yml --target lobby-pc-01Preflight validates the plan for windows-powershell and creates the bundle
without connecting to lobby-pc-01. This allows a macOS or Linux controller
to stage Windows playbooks for an unreachable host. If platform is omitted,
Preflight connects to the selected host to discover its OS and architecture.
By default bundles are written under dist/bundles/.
Choose another output directory if needed:
preflight stage playbooks/lobby.yml --bundle-output-dir ./out/bundlesPreflight creates one bundle per resolved target, not one site-wide archive.
3. Understand What The Bundle Contains
Each bundle is a zip archive that contains:
manifest.jsonplan.json- any referenced plugin executables under
plugins/ - any bundled secret payloads under
secrets/when the staged plan referencessecret:<name>values
The manifest records:
- playbook name
- target name
- target OS and architecture
- build metadata for the staging binary
- referenced modules
- checksums
- lockfile entries for fetched remote actions
This design keeps staged execution reproducible. The offline machine runs the exact task DAG and module structure that was staged. Expressions in when, task name templates, and parameters that reference facts, env, or target.* values are rendered at apply time against the target.
4. Transfer The Correct Bundle
Copy the target-specific zip to the isolated machine using your normal transfer method.
If you staged more than one target, make sure each machine receives its own bundle. The plan inside the bundle is already target-specific.
5. Apply The Bundle Offline
Run:
preflight apply --bundle dist/bundles/<bundle>.zipChoose a custom state file if you want:
preflight apply --bundle ./bundle.zip --state-file ./state/offline.jsonIf the bundle includes encrypted secrets, pass the target identity:
preflight apply --bundle ./bundle.zip --secret-identity .age/target-keys.txtBundle apply reads plan.json directly from the archive, extracts the payload to a temporary directory, builds a module registry from the bundled plugins, and then executes the plan locally with the installed preflight binary on that machine.
6. Know The Important Limits
Staging fails when:
- a task would require embedding a decrypted secret value
- the plan references an unknown module
- a referenced plugin cannot be initialized, reports the wrong logical name, or cannot be copied into the bundle
- a referenced plugin is being staged for an OS or architecture different from the controller
Offline apply does not:
- re-fetch actions
- re-read the source playbook
- rediscover plugins from your normal global plugin directories
That isolation is a feature. It keeps the staged artifact predictable and independent from the original project checkout.
Troubleshooting
Staging fails because of secrets
The runner refuses to embed secret values into a staged bundle. Use encrypted bundle secrets with a target identity, or refactor the playbook so the staged run does not depend on those secret-bearing parameters.
A plugin works normally but not when staged
Confirm the plugin is discoverable before staging:
preflight plugin listOnly plugins actually referenced by the staged plan are copied into the bundle, and each referenced plugin is initialized during staging to verify that its reported logical name matches the module name used by the plan.
Cross-platform staging currently supports built-in modules only. A plugin bundle must be staged on the same OS and architecture as the destination because plugin discovery provides the controller-native executable.
I am not sure which bundle belongs to which host
The filename includes the playbook name, target name, target OS, and target architecture. The same values also appear in manifest.json.