PARALLAX

Prerequisites

What the controller and the target hosts need before you run anything

Parallax’s headline claim is that target hosts need no Python. That is true, and it is worth being precise about what it costs: the work Ansible does with a Python interpreter on the target, Parallax does with a Go toolchain on the controller. Read this page before installing.

Controller requirements

A Go toolchain is required at runtime — not just to build

This is the single most surprising requirement, so it comes first.

Parallax modules are Go programs. They are not shipped inside the parallax binary as precompiled payloads; they are compiled on the controller, on demand, for the architecture of each target host, and the resulting static binary is copied over and executed. pkg/module/compiler/compiler.go shells out to go build to do this.

The compile is roughly:

bash
CGO_ENABLED=0 GOOS=<target-os> GOARCH=<target-arch> \
  go build -tags netgo,osusergo -ldflags "-s -w -extldflags '-static'" -o <module> .

netgo and osusergo plus CGO_ENABLED=0 are what make the module a fully static binary with no libc, DNS-resolver or NSS dependency on the target — that is the mechanism behind “no Python, no runtime dependencies on the target”.

So on the controller you need:

RequirementNotes
Go 1.25 or newerMust be on PATH as go. Match the version in go.mod.
Network access to the Go module proxygo mod tidy runs when generating a module. See Offline operation.
Writable GOCACHESee Warm your build cache.
A writable temp directoryEach compile gets its own os.MkdirTemp scratch directory.

If go is not on PATH, the first task that needs a module fails with:

text
parallax requires a Go toolchain on the controller to compile modules,
but 'go' was not found on PATH: exec: "go": executable file not found in $PATH

That check is a preflight in the compiler, so the failure is reported up front rather than surfacing as an opaque exec error somewhere inside module execution.

Warm your build cache

Compilation happens per module, per target GOOS/GOARCH. A cold GOCACHE means the standard library gets compiled for that target before your module does, which turns a sub-second step into tens of seconds. A warm cache turns it back into milliseconds. This is the difference between Parallax feeling instant and feeling broken, so it is worth arranging deliberately:

  • Do not wipe GOCACHE between runs. In CI, cache the GOCACHE directory (go env GOCACHE, usually ~/.cache/go-build) along with the module cache.

  • In a container image, pre-warm the cache at build time for the architectures you actually target:

    dockerfile
    RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo,osusergo std && \
        CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -tags netgo,osusergo std
  • Parallax also caches the compiled module binaries themselves (--cache, on by default, in ~/.parallax/module-cache). That is a separate layer above the Go build cache, and both matter.

Matching module SDK source

Generated modules import the SDK source embedded in the controller binary. This keeps release builds paired with the SDK they were built against, including when the controller was compiled with -trimpath and is running outside a checkout.

For development or managed installations, PARALLAX_MODULE_SDK_PATH can select an existing SDK directory. The resolver also checks the original source path when available and /usr/local/share/parallax/module-sdk or /opt/parallax/module-sdk, then extracts the embedded matching source as a fallback. Overrides are the operator’s responsibility and should match the controller version.

The embedded source does not include the Go toolchain or every dependency’s module cache. The first build may still download dependencies. The project Dockerfile includes Go and SDK source for controller use.

Offline operation

GenerateCode runs go mod tidy in the generated module directory unless PARALLAX_OFFLINE is set. Setting it also swaps go fmt for a direct gofmt invocation, avoiding the toolchain’s module resolution entirely:

bash
export PARALLAX_OFFLINE=1

Use this only when the SDK path resolves locally and the module’s imports are all standard library or already in the local module cache — PARALLAX_OFFLINE skips dependency resolution, it does not vendor anything for you.

Reference setup

acceptance/docker/ubuntu.dockerfile is the known-good configuration and is worth copying:

dockerfile
# Go toolchain from the official distribution, on PATH
ENV GOLANG_VERSION 1.25.12
ENV PATH /usr/local/go/bin:${PATH}
RUN wget -q https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz -O go.tgz \
    && tar -C /usr/local -xzf go.tgz \
    && rm go.tgz

# Module SDK source, at the path the resolver already knows about,
# and named explicitly so it does not depend on the resolver's fallbacks
COPY module-sdk /usr/local/share/parallax/module-sdk
ENV PARALLAX_MODULE_SDK_PATH=/usr/local/share/parallax/module-sdk

Note that it does both: it installs to a known system path and sets the environment variable. That belt-and-braces approach is the recommendation.

Target host requirements

Targets need no Python, no agent and no Parallax installation. They do need:

RequirementWhy
An SSH server, reachable from the controllerDefault transport. local, docker and winrm connections are also available.
A shell and a writable temp directoryCompiled module binaries are copied in, executed and removed.
An architecture the controller can cross-compile forAny Go-supported GOOS/GOARCH pair.
sudo / su / doasOnly if you use become.

Module-specific target requirements

Most modules are self-contained static binaries and shell out to nothing. The exceptions are worth knowing about:

ModuleNeeds on the targetNotes
unarchivetar for .tar, .tar.gz, .tar.bz2, .tar.xz; gunzip for a bare .gzZip archives are handled natively via archive/zip and need nothing.
apt, yum, dnf, pip, …The corresponding package managerExpected.
service, systemdThe corresponding init systemExpected.

Note: unarchive’s use of external tar and gunzip reflects current behaviour. Native extraction is in progress; when it lands, this row drops to “nothing” and this note should be removed.

Quick check

On the controller:

bash
go version                       # must print 1.25 or newer
go env GOCACHE                   # must be writable, and ideally already populated
echo "${PARALLAX_MODULE_SDK_PATH:-<unset>}"
ls /usr/local/share/parallax/module-sdk 2>/dev/null || echo "not installed system-wide"

If PARALLAX_MODULE_SDK_PATH is unset and no system path exists and you are running a downloaded release binary, fix that before going further — see The -trimpath trap.

On a target, from the controller:

bash
parallax play -i inventory.ini --check ping.yml

The first run is the slow one; it populates both the Go build cache and the module binary cache.

Next steps

Search
    ↑ ↓ move↵ openesc close