Skip to content

Transport and deploy targets

Punix lets you deploy to different places — your local machine, a remote SSH host, a sandbox directory for testing — by changing one configuration choice. The rest of the system (composition, realisation, manifests, rollback, GC) is unchanged across targets.

This page is what targets exist today and how to pick one.

The shipping targets

Local filesystem (default)

punix service deploy MyStack --file stack.pcl

Writes to the real filesystem. Most operations under /etc/systemd/system/ need root.

Remote host via SSH

punix service deploy MyStack \
  --file stack.pcl \
  --target ssh://deploy@prod.example.com \
  --key ~/.ssh/punix-deploy

Punix connects to the target host via ssh, pushes the closure via rsync, writes config files via ssh + cat, and flips the live symlink via mv over ssh (which invokes rename(2) on the remote — same atomicity guarantee as the local case).

Strict host-key checking is always on. Pin a per-environment known_hosts with --known-hosts /path/to/known_hosts for CI / automated deploys.

Hermetic sandbox (for testing)

punix service deploy MyStack \
  --file stack.pcl \
  --transport-root /tmp/test-host

The entire filesystem surface is rebased under /tmp/test-host. /etc/systemd/system/api.service lands at /tmp/test-host/etc/systemd/system/api.service. No root required; no real /etc touched. This is what Punix's own conformance suite uses, and it's available for your own deploy validation.

What "transport-agnostic" means in practice

The same generation manifest, the same atomic-flip guarantee, the same rollback flow apply whether you're deploying locally or over SSH. There isn't a "remote mode" that behaves subtly differently — it's the same code path, with the IO routed through SSH instead of through local syscalls.

Operational consequences:

  • Bugs surface uniformly. If atomic rollback works locally, it works over SSH. The conformance suite explicitly proves the same five atomic-update properties under both transports; if a bug did sneak into one but not the other, CI would catch it.
  • You can practice rollback against a sandbox before you need it in anger. --transport-root lets you stage a full deploy/rollback round-trip locally without touching anything real.
  • Cross-host promotion is bandwidth-efficient. The closure push step asks the target "do you have this content-addressed path?" — if yes, it skips. Re-deploying the same stack across many machines transfers only the missing bytes.

Closure push: how it works

When you deploy over SSH, Punix walks the stack's pinned store paths and pushes anything the target doesn't already have:

deployed: stack MyStack → gen-002 (3 config file(s), 5 store path(s) pinned, 1 pushed/4 cached)

The deploy summary tells you exactly how much had to travel:

  • 1 pushed — one store path's worth of bytes was rsync'd to the target.
  • 4 cached — four store paths were already on the target (because they had the same content-hash); no transfer.

For "same machine" deploys (e.g. --target ssh://localhost to a localhost-sshd), every push is a cache hit — the deploy transfers zero bytes of closure.

For a real cross-host first deploy, every path is pushed. For incremental deploys after that, only changed paths transfer.

Limitations to be aware of

Lifecycle commands are not yet invoked

punix service deploy STACK writes the unit files and flips the symlink. It does NOT run systemctl daemon-reload or systemctl restart — the operator does that. The transport layer supports running arbitrary commands on the target, so this is about CLI wiring, not a structural limitation; Stage 8 closes the loop.

Cross-arch deploys need a same-arch build farm

If your dev host is x86_64 and your target is aarch64, the closure has been built for the wrong arch. The deploy will succeed (the bytes push cleanly) but the services will fail at start time with "exec format error". For now: build on a host with the right arch (an aarch64 jumpbox), then deploy from there.

A future "build on the remote target" path is on the roadmap.

Service environment is the only resolved-at-deploy field

A few PCL fields could in principle be resolved at deploy time rather than build time (e.g. fetching authenticated sources with a deploy-time token). v0.6 wires this only for Service.environment; expanding the surface is roadmap.

Future targets

v0.6 ships local + SSH. Planned (Stage 8):

  • launchd for macOS daemons.
  • supervisord for process supervision without systemd.
  • docker-compose for container deployments.
  • User-mode systemd (writing to ~/.config/systemd/user/ instead of /etc/systemd/system/), enabling non-root SSH deploys to standard systemd hosts.

Each is implemented as a new generator behind the same transport abstraction. The deploy / rollback / manifest contract doesn't change.