How do I deploy the service runtime to a remote WSL host?
Table of Contents
This is the first-class replacement for the manual podman
build / save / ssh … load / run-pod.sh sequence used to deploy to
Newton in sprint 24 (see the
Offload service and DB runtime to a WSL host over SSH story and its
sprint-24 sibling's session notes). Two deployment roles are supported:
a runtime role that runs the full service/DB environment, and a
compute role that runs a partial environment with just an
ores.compute.wrapper compute node, connecting outward to the serving
environment's NATS core.
1. Question
How do I deploy the service runtime to a remote WSL host over SSH?
2. Answer
One command, per role:
# Full environment (service-runtime + NATS containers, Postgres native # on the WSL host): build → stage → transfer → remote up compass env deploy newton # Partial environment — just a compute node (ores.compute.wrapper), # serving some environment's ores.compute.core compass env deploy newton --role compute # Tear down (role-appropriate; --purge also drops the certs volume) compass env deploy newton --stop compass env deploy newton --role compute --stop # One-time host prep: podman present, linger enabled, user dbus socket # up (idempotent — safe to re-run) compass env deploy newton --setup-host # What hosts do we know about? compass env deploy list
The first deploy to a new host generates its profile, .env.<host> at
the repo root (e.g. .env.newton), from the current environment via
the sprint-24 sed rewrites: every *_DB_PORT matching the local
ORES_DB_PORT becomes the remote port (5433 by default — Postgres is
native on the WSL host), and every absolute path under the checkout
becomes $ORES_REMOTE_ROOT (the WSL host has no checkout of this
repo). Inspect and tweak the profile, then re-run:
# .env.newton — the remote config block (all optional): ORES_REMOTE_HOST=marco@192.168.1.22 # ssh target (default: the alias) ORES_REMOTE_ROOT=~/ores-deploy # remote deploy root (expanded on # first deploy, pinned back into # the profile) ORES_REMOTE_DB_PORT=5433 # remote Postgres port
Because the profile is a named env file, every other compass command
can target the same host: compass db recreate -y -k --env newton,
compass services status --env newton, etc.
The compute role additionally needs the serving environment's details in the profile (the wrapper is NATS/TLS-only — no DB credentials):
ORES_COMPUTE_HOST_ID=<uuid> # row in the serving env's compute.hosts ORES_COMPUTE_TENANT_ID=<tenant> # required by the wrapper parser ORES_COMPUTE_NATS_URL=nats://<serving-host>:4222 ORES_COMPUTE_NATS_SUBJECT_PREFIX=ores.<serving-env-label> ORES_COMPUTE_NATS_WIRE_FORMAT=json ORES_COMPUTE_NATS_TLS_CA=/path/in/serving/checkout/ca.crt ORES_COMPUTE_NATS_TLS_CERT=/path/in/serving/checkout/client.crt ORES_COMPUTE_NATS_TLS_KEY=/path/in/serving/checkout/client.key ORES_COMPUTE_HTTP_BASE_URL=http://<serving-host>:8080 # optional
The TLS file paths point at the serving environment's checkout (the compute node authenticates to the serving environment's NATS server); they are copied into the transfer with their basenames.
3. Script
docker/remote-run.sh/docker/remote-stop.sh— the remote side, executed on the target host viassh <host> bash -swithREMOTE_ROOTandROLEas inline assignments.projects/ores.compass/src/env_deploy.py— the orchestrator (profile generation, staging, image build, transfer, invocation).
4. How it works
4.1. Pipeline (runtime role)
- Profile: read
.env.<host>, generating it from.env→docker/.envif missing (rewrites above), then expanding a~-prefixedORES_REMOTE_ROOTagainst the remoteHOMEand pinning the expansion back into the profile (podman's--env-fileand the C++ parsers never expand~). - Stage:
docker/stage-runtime.sh(default mode — every supervised service binary plus its ldd closure). - Build:
podman build --format dockerforlocalhost/ores-service-runtime:localandlocalhost/ores-nats:local(the--format dockeris required — OCI silently drops the Dockerfile's HEALTHCHECK). - Transfer:
podman save ... | gzip -1 | ssh <host> podman load(load auto-detects the gzip stream); the rendered NATS config (path-rewritten copy), the NATS client certs, the IAM JWT key and the profile arescp'd to$REMOTE_ROOTunder the mirrored layout (build/config/...,build/keys/...,docker/.env). - Remote up: both containers run with
--network=host(the Newton-proven model: Postgres is native on the WSL host atlocalhost:5433, unreachable through a pod's slirp4netns — no pod object, no-pmappings), the services container passes--user <uid>:<gid>explicitly (no pod to carry--userns=keep-id; defaults queried from the host). NATS client certs are staged into a podman-managed volume (the bind-mount TLS quirk), the JWT key goes via--envfrom the real PEM file (--env-filecannot hold a multi-line value).
4.2. Pipeline (compute role)
Same stage/build/transfer machinery, but docker/stage-runtime.sh
--service ores.compute.wrapper plus the Dockerfile's existing
SERVICE_NAME build-arg produce localhost/ores-compute-wrapper:local.
The node env file is an ORES_COMPUTE_WRAPPER_NATS_* block — app-
prefixed, because the wrapper's parser maps its environment via
make_mapper("COMPUTE_WRAPPER") and the TLS trio is deliberately
excluded from the shared-domain fallback — and the required argv pair
--host-id / --tenant-id comes from the profile. The container runs
with --network=host, its client certs through a volume, and its
--work-dir under $REMOTE_ROOT/compute/work. No NATS sidecar, no
Postgres, no service set.
4.3. Idempotency
Re-running any command is safe: containers are rm -f'd before
recreate, files are overwritten, volumes are recreated, and the image
build is layer-cached. Picking up a new build is just
compass env deploy <host> again.
4.4. One-time host setup (--setup-host)
podman must be installed on the host (the command fails with
instructions if not), then it enables the user's linger (so rootless
podman keeps working after logout) and starts the user dbus socket
(the sprint-24 Newton finding — a non-login ssh shell may not inherit
XDG_RUNTIME_DIR; the remote scripts set it defensively).
5. Tested by
projects/ores.compass/tests/test_env_deploy.py(pytest — profile generation rewrites, env parsing, compute env writing, transfer pipeline composition) runs in CI.- The sprint-24 Newton deploy-and-verify session exercised every step
of this pipeline by hand; the cutover to running the dev environment
on Newton day-to-day via
compass env deployis the story's end-to-end acceptance.
6. See also
- How do I use named env files? — the
--env <host>registry this recipe's profiles live in. - How do I initialise the NATS server config with compass? — the
compass nats initstep that renders the config this recipe transfers. - Story: Offload service and DB runtime to a WSL host over SSH — the parent story and its decision trail.